Ты неверно понимаешь, как работает overload. Это тупо подсказка для IDE - "эту функцию можно вызывать вот так, и она вернёт вот это, или же вот этак, и она вернёт вон то". Тело функции, завёрнутой в overload,
игнорируется. А последнее объявление функции не должно иметь overload, и оно как раз и будет реализовывать все варианты. Так что итоговая реализация всё равно будет иметь if внутри. Да и в твоём случае overload никчему, так как другие-то параметры не отличаются.
Ты мог бы, конечно, упороться, и сделать
functools.singledispatch - при условии, что ты message переставишь первым параметром. Но, имхо, в этом нет практической необходимости, всё это делается в разы проще.
async def show_cpanel(
state: FSMContext,
message_source: typing.Union[Message, CallbackQuery] # значение одного из двух типов, но обязательное!
) -> None:
'''
Send control panel to current user
'''
this_user = message_source.from_user.id # и Message, и CallbackQuery имеют from_user
message = message if isinstance(message_source, Message) else message_source.message
profile_is_visible = await req.check_profile_visible(this_user)
message_id = (await message.answer(
'<b>' + _('Панель управления') + '</b>',
parse_mode='HTML',
reply_markup=kb.control_panel(profile_visible=profile_is_visible)
)).message_id
await state.update_data(cpanel_message_id=message_id)
logger.info(f'Admin #{this_user} opened control panel')
Вот и всё. Твой вариант
callback_query: Optional[CallbackQuery] = None,
message: Optional[Message] = None,
плох тем, что из него совершенно не очевидно, что хотя бы один из параметров не должен быть None.
EDIT: Хотя я не вполне понял идею насчёт callback.message. Если я верно помню,
этот атрибут хранит ссылку на сообщение от бота, содержащее кнопку, для которой был вызван callback. Поэтому его from_user по идее будет заведомо указывать на бота. Так что да, лучше последуй совету
Everything_is_bad и переделай функцию так, чтобы она принимала пользователя и сообщение отдельными параметрами. А их значения определяй там, где ты функцию вызываешь.