-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Auto Pagination for answer_inline_query #2072
Conversation
We might want to allow for pagination with less then 50 items per page. That should be useful for use cases where the computation of each results takes long. |
Actually, in the case |
**kwargs): | ||
""" | ||
Use this method to send answers to an inline query. No more than 50 results per query are | ||
allowed. | ||
|
||
Args: | ||
inline_query_id (:obj:`str`): Unique identifier for the answered query. | ||
results (List[:class:`telegram.InlineQueryResult`)]: A list of results for the inline | ||
query. | ||
results (List[:class:`telegram.InlineQueryResult`] | Callable): A list of results for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are generic type annotations possible for callable in docstrings?
Callable[[int], Optional[List[:class:`telegram.InlineResult`]]]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when user returns empty list?
- Clarify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E.g. for callbacks we also just state Callable
and state the signature explicitly in the docstring (see Handler). Personally, I don't find Callable[[int], Optional[List[:class:
telegram.InlineResult]]]
overly readible and newbies will not understand it.
What happens when user returns empty list?
»or :obj:None
, if there are no more results.« Is that ambiguous
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's also Protocol
:
class ResultsGenerator(typing.Protocol):
def __call__(self, current_index: int) -> typing.List[telegram.InlineResult]:
...
But would be quite revolutionary to add that, and even more verbose ;) So just FYI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't find Callable[[int], Optional[List[:class:telegram.InlineResult]]] overly readible
Yea, generally that syntax is awful. Takes a bit of getting used to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
»or :obj:None, if there are no more results.« Is that ambiguous
Maybe something along the lines of "or an expression that evaluates to False" (bad wording, I know, but you get the gist)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But would be quite revolutionary to add that, and even more verbose ;) So just FYI
good to know :)
Maybe something along the lines of "or an expression that evaluates to False" (bad wording, I know, but you get the gist)
I'd rather leave that as implementation detail. Documenting it might lead to problems, if the implementation needs to be changed and None
is easier to graps for newbies
if current_offset == '': | ||
current_offset = 0 | ||
else: | ||
current_offset = int(current_offset) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Account for negative value with max(current_offset, 0)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In what case would we have a negative value here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defensive coding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO it would just make debugging very hard, if invalid negative input lead to completely unexpected behaivour instead of leading to an exception thrown … I'll add a warning, that current_offset
shouldn't usually be passed manually bot InlineQuery.answer(, auto_pagination=True)
should take care of it.
telegram/bot.py
Outdated
next_offset = current_offset + 1 | ||
results = results[ | ||
current_offset * MAX_INLINE_QUERY_RESULTS: | ||
current_offset * MAX_INLINE_QUERY_RESULTS + MAX_INLINE_QUERY_RESULTS] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could just use next_offset * MAX
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually next_offset
could be used in a few places here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see how to use it here, but I don't see where else to use it …
@@ -385,6 +400,97 @@ def test(_, url, data, *args, **kwargs): | |||
switch_pm_text='switch pm', | |||
switch_pm_parameter='start_pm') | |||
|
|||
def test_answer_inline_query_current_offset_error(self, bot, inline_results): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this could become a lot more concise using @pytest.mark.parametrize
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I understand you here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://docs.pytest.org/en/latest/parametrize.html#pytest-mark-parametrize
This generates new tests given a bunch of parameter combinations so that you don't need to make multiple assertions in a single test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's an example for classs-based test method parametrization:
class TestCase:
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
("6*9", 42), # fails
])
def test_eval_combinations(self, test_input, expected):
assert eval(test_input) == expected
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know that ;) I just don't see what you'd want to parametrize on this test
telegram/inline/inlinequery.py
Outdated
bot.answer_inline_query(update.inline_query.id, | ||
*args, | ||
current_offset = self.offset if auto_pagination else None, | ||
**kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bot.answer_inline_query(update.inline_query.id, | |
*args, | |
current_offset = self.offset if auto_pagination else None, | |
**kwargs) | |
bot.answer_inline_query( | |
update.inline_query.id, | |
*args, | |
current_offset=self.offset if auto_pagination else None, | |
**kwargs, | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that is a matter of taste … And I'd prefer to stick to the style we use in all the other docstrings
telegram/bot.py
Outdated
if not results: | ||
results = [] | ||
else: | ||
next_offset = current_offset + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why there is an else
block here.
- Explain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# callback returned None, i.e. there are no more results. We still need to answer the query to tell TG that.
# so we pass an empty results list along with next_offset = '', which tells TG to not ask again
if not results:
results = []
# callback returned more results, so we need to assume that there are results for the next page as well
# hence, increase next_offset. If there are no more results fo the next page, the next query will trigger
# the `if not results` clause above
else:
next_offset = current_offset + 1
bot.answer_inline_query(1234, | ||
results=inline_results, | ||
next_offset=42, | ||
current_offset=51) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bot.answer_inline_query(1234, | |
results=inline_results, | |
next_offset=42, | |
current_offset=51) | |
bot.answer_inline_query( | |
1234, | |
results=inline_results, | |
next_offset=42, | |
current_offset=51 | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, we use that style all over the place. Either we change the style completely or not at all, IMHO
tests/test_bot.py
Outdated
next_offset=42, | ||
current_offset=51) | ||
|
||
def test_answer_inline_query_current_offset_1(self, monkeypatch, bot, inline_results): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again a case for parametrization. There are mutliple assertions in this test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here i see it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about amending the inline query example? 😄
- Think about adding an example
tests/test_bot.py
Outdated
next_offset=42, | ||
current_offset=51) | ||
|
||
def test_answer_inline_query_current_offset_1(self, monkeypatch, bot, inline_results): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's with the 1 / 2 numbers in the test names?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in test 2 I test that everything works out, if there are less than 50 results already in the first call, which is not covered by 1. Seemed a bit long to describe in a function name …
Thanks for the thorough review! I left replies and resolved a few of the more obvious ones. |
As discussed in #2071.
Closes #2071