Skip to content
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

StatesV2: Support for topics, multibots, and business messages #2321

Merged
merged 26 commits into from
Aug 11, 2024

Conversation

coder2020official
Copy link
Collaborator

@coder2020official coder2020official commented Jun 23, 2024

Only sync memorystorage working now

@coder2020official
Copy link
Collaborator Author

@Badiboy I might need your opinion for further development. Have you used states before I ask you a question?

@Badiboy
Copy link
Collaborator

Badiboy commented Jun 25, 2024

@coder2020official I never used States :(

@coder2020official
Copy link
Collaborator Author

Finished for sync - only left thing is to add a "bridge" between old and new formats of states to ease switching between these two.

…t to avoid conflicts, added support of statesv2 to async(only memory storage)
self.bot_id = None

if token_check:
result = apihelper.get_me(token)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Badiboy do you think this is a good idea

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically due to sync init, I had to call getMe from apihelper to obtain types.User due to token_check being true(new param)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So basically due to sync init, I had to call getMe from apihelper to obtain types.User due to token_check being true(new param)

Looks a bit ugly, but functional.

@coder2020official
Copy link
Collaborator Author

coder2020official commented Jul 19, 2024

Checklist:

  • Rewrite sync pickle storage to use decorators for locks to follow DRY
  • Add a method for redis storage to migrate from old to new storage type
  • test with old code example to ensure old code compatibility
  • Compare old methods with new methods for all storages to check for return types
  • Check typehints
  • Add comments to needed functions & classes to provide an overview and usage
  • state="*" should check for existing state and not return true in filters; BREAKING
  • Update example to ensure better state experience
  • Discuss the synchronous getMe call with @ badiboy for async
  • Discuss the file structure implementation and file names(e.g states, aio & sync folder names, maybe better asyncio?)
  • Code cleanup
  • Should remove aioredis from optional dependencies: remove optional dependency aioredis #2195

@coder2020official
Copy link
Collaborator Author

Pickle storage is the worst out of all. I had some bugs when simultaneously testing my states code.
I will investigate the issue and add a warning that pickle should NOT be used for production. Maybe for testing to a small audience.

@coder2020official
Copy link
Collaborator Author

coder2020official commented Jul 19, 2024

Breaking changes will be here

  1. state="*" filter will check for existing state, instead of returning True in StateFilter, which is useless;
  2. get_data returns an EMPTY dictionary instead of None if the key does not exist.
  3. State storage format completely changed: in order to avoid state loss, StateRedisStorage().migrate_format(bot_id) should be used (only for redis as it is supposed to be the only storage used for production)
  4. StateContext was renamed into StateDataContext; It should not impact users unless they deliberately use it for some reason(idk); Though in this case none of our examples featured this class directly, nor it was recommended to use

@coder2020official
Copy link
Collaborator Author

coder2020official commented Jul 21, 2024

@Badiboy i need your opinion on something I addressed before. Check the comment above(about sync call in async, I tagged you there)

…y redis as it should be used for production.
@coder2020official
Copy link
Collaborator Author

@Badiboy I am nearly done with this PR.
I have provided all breaking changes which I believe are all reasonable; If they were not - I provided a way to fix backward compatibility(e.g migrate_format for Redis storage to avoid data loss)

Now, I would like you to review:

  • Folder & file names. I have moved states to a folder called states, with aio and sync folders. Do you think these names are fine? Maybe I should rename "aio" to "asyncio"?
  • the whole code :) (or at least the main files - init.py, async_telebot.py, etc to ensure backward compatibility, etc)

@coder2020official
Copy link
Collaborator Author

All that is left is cleaning up the code, which I will do after you proceed with the review

@coder2020official coder2020official marked this pull request as ready for review July 25, 2024 13:26
@coder2020official coder2020official requested review from Badiboy and removed request for Badiboy July 27, 2024 10:14
@coder2020official
Copy link
Collaborator Author

@Badiboy I am fully done, need your review

@Badiboy
Copy link
Collaborator

Badiboy commented Jul 27, 2024

@coder2020official I saw the request but I'm not sure when I'll be able to pass all of this:

image

Especially as far as I never used states.

@coder2020official
Copy link
Collaborator Author

coder2020official commented Jul 27, 2024

You can review only crucial files(not new ones, etc), e.g init, async_telebot.py, etc
All new files or storages do not require a review

@coder2020official
Copy link
Collaborator Author

Also I have addressed all breaking changes above

if validate_token:
util.validate_token(self.token)

self.bot_id = util.extract_bot_id(self.token) # subject to change in future, unspecified
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May cause error if token is not in valid form. extract_bot_id should return NONE if extract fails.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But validate_token raises an error if token is not in valid form?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. But only if validate_token is enabled. It's optional.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yes. Btw, you are fine with validate_token being True by default?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw it. I'm not happy, but as far it is not breaking and can be disabled - I can deal with it.

@@ -1172,6 +1184,9 @@ def polling(self, non_stop: Optional[bool]=False, skip_pending: Optional[bool]=F
if restart_on_change:
self._setup_change_detector(path_to_watch)

if not self._user:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed that get_me should be optional.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, my bad, for sync there's no need for this as self.user is a property that already does this

"""
Reset data for a user in chat.
Reset data for a user in chat: sets the 'data' fieldi to an empty dictionary.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fieldi => field?

if validate_token:
util.validate_token(self.token)

self.bot_id: int = util.extract_bot_id(self.token) # subject to change, unspecified
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May cause error if token is not in valid form. extract_bot_id should return NONE if extract fails.

@@ -424,7 +437,8 @@ async def _process_polling(self, non_stop: bool=False, interval: int=0, timeout:
# show warning
logger.warning("Setting non_stop to False will stop polling on API and system exceptions.")

self._user = await self.get_me()
if not self._user:
self._user = await self.get_me()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed that get_me should be optional.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will return this to the original version, where it is called by default?

@Badiboy
Copy link
Collaborator

Badiboy commented Jul 28, 2024

@coder2020official I checked only main files. All related to states are on your responcibility )

@coder2020official
Copy link
Collaborator Author

Anything else left?

@Badiboy
Copy link
Collaborator

Badiboy commented Jul 28, 2024

I don't understand your last commits.

image
bot_id is available only for ones who used validate_token? Independently if token is valid or not? As I noted before, I propose still trying to get bot_id, but just handle the case when it cannot be extracted. It's not too hard.

@Badiboy
Copy link
Collaborator

Badiboy commented Jul 28, 2024

You totally removed get_me from sync:
image
And make it every time in async
image

Instead of make it optional in both versions?

@coder2020official
Copy link
Collaborator Author

I returned them to their original versions.
Removing in sync will still cause a request to be done, as self.user will be empty, and it is a property.

Before, async would always call a getme request

@coder2020official
Copy link
Collaborator Author

For 1), ok will fix it

@coder2020official
Copy link
Collaborator Author

fixed 1), addressed 2)

@Badiboy
Copy link
Collaborator

Badiboy commented Jul 30, 2024

LGTM, let's go )

@coder2020official
Copy link
Collaborator Author

merge?

@coder2020official
Copy link
Collaborator Author

@Badiboy bot api update was released just now.
I propose first releasing bot API update, then merge this

@coder2020official
Copy link
Collaborator Author

Need to change version to 4.23.0

@Badiboy
Copy link
Collaborator

Badiboy commented Aug 5, 2024

Have nothing against it. Push it here and merge.

@coder2020official
Copy link
Collaborator Author

I am afk, so we will have to delay this a little
No reason to rush anyways

@coder2020official
Copy link
Collaborator Author

@Badiboy done, merging since you approved.
Next version will need to be 4.23.0

@coder2020official coder2020official merged commit 21e92fb into eternnoir:master Aug 11, 2024
7 checks passed
@coder2020official
Copy link
Collaborator Author

We will also need a special changelog for next version with changes specifically for states.

@coder2020official
Copy link
Collaborator Author

States

Breaking changes

  • state="*" filter now checks if state exists.
  • get_data returns an empty dictionary if record does not exist
  • State storage format completely changed: in order to avoid state loss, StateRedisStorage().migrate_format(bot_id) should be used (only for redis as it is supposed to be the only storage used for production)

Features of new states

  • Support for business bots
  • Support for topics
  • Support for multibots

To automatically set business_message_id or message_thread_id, check for examples that use StateContext:
https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/custom_states.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants