Repository: python-telegram-bot/python-telegram-bot

Issue:
[BUG] Improve readability of nested conversation example
### Issue I am facing
Tried to understand the example of the nested conversation handler example https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/nestedconversationbot.py

At the bottom, lines 354 - 357, there is this code snippet:
```
# Because the states of the third level conversation map to the ones of the
# second level conversation, we need to be a bit hacky about that:
conv_handler.states[SELECTING_LEVEL] = conv_handler.states[SELECTING_ACTION]
conv_handler.states[STOPPING] = conv_handler.entry_points
```

1) What does the conversation state assignment do?
2) Why is it needed?
3) Why is it 'hacky'?


I asked the same question in the telegram group, but my post got lost amongst messages of 5000 channel members.



Discussion:
> 1. What does the conversation state assignment do?

It takes the states defined in the initalization of `conv_handler` and copies them. So the definition of `conv_handler` is equivalent to

```python
selection_handler = [
    add_member_conv,
    CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
    CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
    CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
]
conv_handler = ConversationHandler(
    entry_points=[CommandHandler('start', start)],

    states={
        SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
        SELECTING_ACTION: selection_handlers,
        SELECTING_LEVEL: selection_handlers,
        DESCRIBING_SELF: [description_conv],
        STOPPING: [CommandHandler('start', start)],
    },

    fallbacks=[CommandHandler('stop', stop)],
)
```

The lines
```python
conv_handler.states[SELECTING_LEVEL] = conv_handler.states[SELECTING_ACTION]
conv_handler.states[STOPPING] = conv_handler.entry_points
```
just save same duplicate code. But tbh, I don't really recall why I designed it that way. More impartantly, as of #1756, this won't even work anymore, as the `states` attribute of `ConversationHandler` is now immutable. So good thing you asked :) I'll convert this one into a bug report and fix it in the next few days.

> 2. Why is it needed?

The `description_conv` is used as nested conversation in both `conv_handler` and `add_member_conv`, but for each state of `description_conv`, we can only set one mapping in `map_to_parent`. So to be able to use `description_conv` directly from `conv_handler`, these lines make sure, the states `SELECTING_LEVEL`, `STOPPING` are available for `conv_handler`, because those are the ones we'll be in when leaving `description_conv`.

> 3. Why is it 'hacky'?

Because just initializing `conv_handler` properly (see 1.) would be cleaner code.



Resolving patch:
diff --git a/examples/nestedconversationbot.py b/examples/nestedconversationbot.py
--- a/examples/nestedconversationbot.py
+++ b/examples/nestedconversationbot.py
@@ -335,26 +335,27 @@ def main():
     )
 
     # Set up top level ConversationHandler (selecting action)
+    # Because the states of the third level conversation map to the ones of the econd level
+    # conversation, we need to make sure the top level conversation can also handle them
+    selection_handlers = [
+        add_member_conv,
+        CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
+        CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
+        CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
+    ]
     conv_handler = ConversationHandler(
         entry_points=[CommandHandler('start', start)],
 
         states={
             SHOWING: [CallbackQueryHandler(start, pattern='^' + str(END) + '$')],
-            SELECTING_ACTION: [
-                add_member_conv,
-                CallbackQueryHandler(show_data, pattern='^' + str(SHOWING) + '$'),
-                CallbackQueryHandler(adding_self, pattern='^' + str(ADDING_SELF) + '$'),
-                CallbackQueryHandler(end, pattern='^' + str(END) + '$'),
-            ],
+            SELECTING_ACTION: selection_handlers,
+            SELECTING_LEVEL: selection_handlers,
             DESCRIBING_SELF: [description_conv],
+            STOPPING: [CommandHandler('start', start)],
         },
 
         fallbacks=[CommandHandler('stop', stop)],
     )
-    # Because the states of the third level conversation map to the ones of the
-    # second level conversation, we need to be a bit hacky about that:
-    conv_handler.states[SELECTING_LEVEL] = conv_handler.states[SELECTING_ACTION]
-    conv_handler.states[STOPPING] = conv_handler.entry_points
 
     dp.add_handler(conv_handler)
 


Test patch: