New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Discussion:] ConversationHandler #2770
Comments
I request for a tree-like behaviour on reentering nested |
The grammy dev recently contacted me and pointed towards their implementation of conversation handlers. Since they are pure async as well, this might be interesting to consider. May be too much for either V20 or PTB in general and rather ptbcontrib, just wanted to write it down. |
Interesting! Thanks for mentioning. A similar idea has come up in #3042 before. While my comments on that PR still hold, one can ofc still keep the concept in mind. Redesigning CH as a whole can probably come to many different results. |
I've read through the grammy docs a bit and have a few comments on what I learned: Most important IMO is the observation, that the async conversation-function is not just simply executed. Instead on every
TL;DR: While this approach allows you to write easy-to-read code in a straightforward way at first glance, it's not quite so easy on second glance and the implementation is everything but easy. A second thing that I want to point out is the usage of Grammys approach definitely has several perks, surely works well in grammys ecosystem and I have huge respect for whoever built it. But my summary is that grammys approach is not systematically superior to a classic FSM. |
Glad to see that these ideas are spreading! It took a fair bit of research and many iterations and brainstorming to come up with the concept. And yes, it was a bit challenging to build, too :) In case you're going to consider something similar for PTB, note that you have control over the event loop in Python (in contrast to JS), which greatly simplifies the way how conversation builder functions are run. You can simply stop running the loop, while grammY conversations sort of have to implicitly intercept its execution via a special promise and then hack ourselves back into control. On the question why I personally dislike FSMs and similar abstractions, there's a good high-level summary of the ambition behind conversations in this interview starting at 26:34 min: https://podrocket.logrocket.com/grammy Looking forward to seeing what you'll come up with! Good luck! |
@KnorpelSenf thanks for your comment! I did a bit more reading and thinking on the topic, but haven't made any significant progress so far :D TBH I also don't quite understand your comment on controlling (and stopping) the event loop in Python. If I stop the event loop, then how will I process new updates? |
A few insights into the grammyjs conversation setup/how something like this could look like in python from offline discussion with KnorpelSenf:
|
I apologize for the possible duplication, I did not find this in the branch. I think it is necessary to add a simple way to get the conversation by name and get the current state from the conversation. main_conversation = context.application.handlers.get_by_name("main_conversation")
state = main_conversation.get_state(CHAT_ID, USER_ID) Maybe I have bad notification design, but I don't need to send a notification when a person has passed a certain state. I used to bypass this with the help of a persist, I took data from the database, now when the persist is not realtime, I have to bypass it with the help of cyclic checking of handlers, which I think is not correct. my current user state code: async def get_state(context: CallbackContext, telegram_id: int):
conversations = None
user_state = None
for handlers in context.application.handlers.values():
for handler in handlers:
if isinstance(handler, ConversationHandler) and handler.name:
# noinspection PyProtectedMember
conversations = handler._conversations
if conversations is not None:
for key, state in conversations.items():
if telegram_id in key:
user_state = state
return user_state |
I had more thoughts on the grammyjs conversation setup. Specifically, the following points from the description above would be hard to realize that way IMO or at least harder than with an FSM approach like we currently have:
OTOH, the following things might be easier with the grammyjs setup:
|
Leaving my 2c here since I'm still subscribed to the thread:
You don't need if-else, to repeat code, you need loops. The convenience methods as well as conversation forms abstract those away most of the time.
This info is always available implicitly. Whatever you want to do at step X you can already do by writing the code at that point in the conversation.
Those patterns are discouraged, they are like the complaint "python is bad because it does not have goto". Code will be better without jumping around.
Already implemented in grammY, but conversations are silently left once expired, which may not always be the best solution for people.
Concurrent update processing per chat is rarely a good idea, not sure what this is about tbh
You must first synchronize updates received from different chats, as this is a race condition. Good luck with this in distributed environments, this would come with heavy perf penalties. But yes, the abstraction itself would permit this easily. |
I would also add to this the action that led to the current state. If I want to return the user to a certain state (and a certain callback), I most likely need to ask them the same question that they were asked before they got to that state. Right now I have to repeat the question manually before returning the desired state and sending the user back to the relevant part of conversation. This can be too tricky, though, as, depending on the state of conversation, we may need to edit an inline query or send a new message or do something else. So maybe it's not realistic anyway. |
I would also like to suggest the possibility of adapting the library for consideration. https://github.com/Tishka17/aiogram_dialog |
Recently the idea came up in chat to allow custom classes in CH, because the subclass restriction of Update potentially forces one to input a lot of dummy values in the class. So if we ask them to e.g. subclass ConversationHandlerRequirement which only has user and/or chat id as attribute, we can decrease that dummy data amount |
@Poolitzer see also the 3rd bullet point in
|
Several ideas for changes and/or additions have been gathered in the v14 project, mentioned in the user group or issues or dwelled in the back of my head. So I'm writing this issue to keep track of them better.
The following list of ideas should be discussed before release of v14 because at least some of them are easier to implement if we do them non-backwards compatible. I'd like to emphasize that most of these are very rough first thoughts and I'm fully aware that we may scrape many of them.
Related: #2802
CH.fallbacks
optional. I've seen a lot of people (including me) setting at to an empty list for some use cases … Poolitzer already expressed support for this ideaConverstanionHandler + run_async: Problem is that we can't persist futures/promises. But we should be able to guarantee that all futures are done on (clean) shutdown. One could add something likeThis already get's handled on v20BasePersistence.resolve_conversation_promises
that persistence classes can call before flushing on shutdown.fallbacks
list, which requires to make all state handlers ignore updates that should be handled by thefallbacks
. see also Added impementation of "prefallbacks" list. #2764 #2764; Implemented "prefallbacks" feature #2960entry_points
,fallbacks
(and eventual "prefallbacks") into thestates
dict. We already have special states forTIMEOUT
andWAITING
, so I think it would make the interface cleaner if we used special states(PRE)FALLBACK
andEND
as keys for these lists rather than making them standalone kwargs of__init__
context.conversation_data
. It should be an object unique per conversation key andConversationHandler
instance. It may even be automatically deleted when the conversation ends . See also [FEATURE] Have per-conversation data (or at least an identifier) in ConversationHandler #4136ConversationHandler
we consider public.conversations
is currently public, although undocumented and basically part of the internals. If we make it private, we can rework the setters ofpersistence
andconversations
to something likeget_persisted_data(persistence)
orinitialize_persistence(persistence)
conversations
works public may help with some of these and also with the abovecontext.conversation_data
telegram.Update
conversation_timeout
:WATING
state, especially with nested conversationsCH.END
topersistence.update_conversation
instead ofNone
per_message=True, per_chat=False
- this is not necessarily valid in caseinline_message_ids
are usedentry_points
should be checked after the states are checked - that way, state handlers may trigger before anyentry_points
trigger viaallow_reentry
. See https://t.me/pythontelegrambotgroup/606782?thread=606781 for a question on that. Edit: On second thought, theallow_reentry
may not be needed at all - the user can just repeat theentry_points
in thefallbacks
. If "prefallbacks" (see above) are added, that even allows the user to customize which of state handlers andentry_points
are checked firstConversationHandler
#3665 (closed only to track this here)ConversationHandler.states
has a key -1 #3834The text was updated successfully, but these errors were encountered: