Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[FEATURE] ConversationHandler: Allow special handlers/callbacks for entering states #2390

@Bibo-Joshi

Description

@Bibo-Joshi

Originally proposed in #2388, rewriting here to summarize.

Motivation

ConversationHandler currently reacts to updates that happen while in a state. This means that a callback needs to "prepare" everything that the next state expects to have happend (e.g. send a message the user needs to react to). This can sometimes make it a bit hard to follow the logic of "what needs to be done where" and can also lead to code duplication in the cases where

  • multiple states lead to the same next state, thus they all need to do the same preparations
  • nested conversations are used that lead back to a parents state thats also reached directly by parent states. Here the child conversation needs to do the same preparations as the parent conversation.

In the setting of nested conversations this also leads to the parent conversation and the child conversation being dependent on each other, making child conversations less reusable for different parents.

Proposed feature

Allow to run code when "entering" a state. This could be either a simple callback(update, context) or (which I would prefer) a list of handlers, just like currently for states. The logic would then be something along the lines:

def handle_update(self, update):
    ...
    new_state = selected_handler(update, context)
    if for handler in self.state_entries.get(new_state, []):
        if handler.check_update(update):
            handler.handle_update(update, context)
            break

The entry handlers could be specified via a entries keyword (or whatever naming) that accepts a dictionary just like states does.

Of course it should be purely optional, because a) backwards compatibility and b) simple conversations will still do fine without it.

Integration with run_async

When a state-handler runs asynchronously, we only get a promise and hence don't know the next state. what we could do is add a method add_done_callback to ext.utils.Promise that makes sure the corresponding callback is run when Promise.run is done.
This is exactly what's provided by asyncio.Task/Future, so this has a high chance of not being an issue in the context of #2288.

Current alternatives

What one can currently do to reduce code duplication is ofc to extract the duplicated code to a standalone function and call that wherever needed. In the setting of child conversations that should be reusable by different parents, one can use a factory pattern, i.e. have a method

def build_child_conversation(extracted_function_1, extracted_function_2, …) -> ConversationHandler:
    …

that inserts the passed functions into the logic of the child conversation. This allows for the highest level of encapsulation. If that's not needed, one can also just directly use the corresponding functions of the parent conversation directly.

While those soltions a viable, they require some manual work and still lead to some extend of code duplication.

Summary & Things to consider

One can surely argue that ConversationHandler is already complex enough and that the added benefit is rather small and mostly interesting for very advanced users who like to design their code as clean as possible. On the other hand I can imagine that this can help some users follow the logic of ConversationHandler more easily and promoting clean code structure is nothing to be ashamed of.

Basically I think this is a reasonable feature to have, but would not be opposed to disregarding it in favor of maintainability.

Ragarding the integration with run_async, it may be worth to just wait for v14 to be out, before tackling this (if we decide to do so).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions