Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Initial FSM PoC#4669

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
Bibo-Joshi wants to merge9 commits intomaster
base:master
Choose a base branch
Loading
fromfeature/fsm
Draft

Initial FSM PoC#4669

Bibo-Joshi wants to merge9 commits intomasterfromfeature/fsm

Conversation

Bibo-Joshi
Copy link
Member

@Bibo-JoshiBibo-Joshi commentedFeb 4, 2025
edited
Loading

Works on#2770

"It has been a while since we openend#2770" would be an understatement. Yet here we are :D
Finally, I got around to draft up a somewhat proper PoC for my ideas outlined in#2770 (comment). I also attached an example that showcases how the FSM setup can be used. You'll need ≥2 accounts to test it.

To be clear: as of 2025-02-04 this PR does by far not include everything mentioned in#2770 (comment). I hope to work on it in the nearish-future. Continued work on this PR could be greatly supported by intermediate reviews, comments & discussions :)


ToDos / Ideas / Thoughts

(Listing here only things that came to my mind explicitly while working on this PR, not copying everything from#2770)

  • Usingcontext.job_queue.run_once for conversation timeouts is relatively easy, but would have to repeated everywhere which also holds for the cancellation. Maybe we can think of a clever way to abstract that for the user.
  • Having both groups and states makes the "who handles what" logic more involved. Do we do "for state in states: for group in state" or "for group in groups: for state in group"? AlsoState.ANY and things likeStateA | StateB make it necessary that one update can be handled by multiple states. All this atleast needs proper documentation about what's going on. Additionally, we should think carefully about what we want here.
  • I'm unsure about ifget_active_key_state should be async or not:
    • I feel like it should not make too much async operations - since it's part of the logic inApp.process_update, it should be as quick as possible
    • If we want to support lazy-loading, this would probably be the place were it comes into play - depending on how we design the persistence connection
    • If states areset withinget_active_key_state (as is done in the fsm example I added), then I'm wondering if one should use a semaphore for that. If one would do that, this could lead to major delays inprocess_update
  • Instead ofBoundedSemaphore(1), we could probably just useLock as synchronization primitive

Palaptin and clot27 reacted with hooray emoji
@Bibo-JoshiBibo-Joshi added 🛠 refactorchange type: refactor 🔌 enhancementpr description: enhancement labelsFeb 4, 2025
@codecovCodecov

This comment was marked as outdated.

Copy link
Member

@PoolitzerPoolitzer left a comment

Choose a reason for hiding this comment

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

Just some first impressions, once I gain a further understanding I can give more

self.admin_id = admin_id
super().__init__()

def _get_admin_state(self) -> tuple[State, int]:
Copy link
Member

Choose a reason for hiding this comment

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

not sure I like this being private/protected/however the underscore is called

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

So far I see no reason why someone should be allowed to allUSM(…).get_admin_state … then again this is only an example, so I don't care :D

super().__init__()

def _get_admin_state(self) -> tuple[State, int]:
return self._states[self.admin_id]
Copy link
Member

Choose a reason for hiding this comment

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

That should not be the private function right

Bibo-Joshi reacted with thumbs up emoji
def _get_admin_state(self) -> tuple[State, int]:
return self._states[self.admin_id]

def get_state_info(self, update: object) -> StateInfo[Optional[int]]:
Copy link
Member

Choose a reason for hiding this comment

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

The type hint here is linked to the FSM class type hint above right? I can't have one be a string one be an integer.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Correct, the type variable specifying the type of keys you use.

Comment on lines +1518 to +1519
if not state_handlers[group]:
del state_handlers[group]
Copy link
Member

Choose a reason for hiding this comment

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

Does this remove the group if its empty? Can we comment it

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

yes, that's basically the same asstate_handlers.pop(group, None), I think. It will callstate_handlers.__del__(group). IMHO this is basic python syntax 😬


from telegram.ext import JobQueue

_KT = TypeVar("_KT", bound=Hashable)
Copy link
Member

Choose a reason for hiding this comment

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

Im back with my thinking about private stuff. Why do we private it here, not in other files; should we. Hm.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I feel you :D ifmachine was a public module liketelegram.constants, it would have to be protected IMO. Since it's not and we expose the relevant elements only viatelegram.ext, it's not so important. Making it clearer what the content exposed by this module is still doesn't hurt.
If you want to go deeper into the rabbit hole:https://discuss.python.org/t/add-the-export-keyword-to-python/28444/6

Comment on lines +165 to +166
application.fsm = UserSupportMachine(admin_id=123456)
application.fsm.set_job_queue(application.job_queue)
Copy link
Member

Choose a reason for hiding this comment

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

these should be part of the builder and the second one set by default imo

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

yes, absolutely. #PoC 👼

return f"FSM_Job_{'_'.join(str(hash(k)) for k in keys)}"

def set_job_queue(self, job_queue: "JobQueue") -> None:
self.__job_queue = weakref.ref(job_queue)
Copy link
Member

Choose a reason for hiding this comment

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

Why weakref wont it/the user experience break if the supplied job queue gets destroyed

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

There isjob_queue.application. that's also weakref b/c we have a cyclic reference:application is application.job_queue.application. Now we also haveapplication is application.fsm.job_queue.application. I'm not 100% sure if this is strictly necessary, but it seemed safer to me 🤔 I can at very least add a comment.

Comment on lines +166 to +167
if raise_exception:
raise RuntimeError("JobQueue was garbage collected")
Copy link
Member

Choose a reason for hiding this comment

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

ha here see. why.

def __init__(self, uid: Optional[str] = None):
effective_uid = uid or uuid4().hex
if effective_uid in self.__knows_uids:
raise ValueError(f"Duplicate UID: {effective_uid} already registered")
Copy link
Member

Choose a reason for hiding this comment

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

is the error saying UID correct? I would understand the optional type hint to be anything hashable

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Sorry, I don't get you. What do you think is wrong? the phrasing of the error message?



class State(abc.ABC):
__knows_uids: ClassVar[set[str]] = set()
Copy link
Member

Choose a reason for hiding this comment

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

Wait I must really misunderstand smth, how does this work across states

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

uh … that's how class variables work? :D In the initself.__knows_uids is basically the same asState.__knows_uids

@Poolitzer
Copy link
Member

A thought I had: Since this is such a huge change for something so integral to our project, should we provide a beta version/implementation after we are satisfied with the status and announce it to get people to try it out and provide feedback before we make a first release?

@Bibo-Joshi
Copy link
MemberAuthor

A thought I had: Since this is such a huge change for something so integral to our project, should we provide a beta version/implementation after we are satisfied with the status and announce it to get people to try it out and provide feedback before we make a first release?

That is indeed a very good idea 👍
My thoughts on this are roughly like this:

  • the overall change should (hopefully) be such that nothing changes if you just don't use the new states.
  • that way we can introduce the refactoring & deprecate the existing CH without braking changes.
  • We can mark the status as "public beta feature" and at some point we can say "well, starting with version 25.0, states are now marked as stable".

that way we wouldn't have to do any additional actual beta releases.
If we prefer to have a proper beta release, I'm open to that. We could e.g. do states update on a 23.0bx line that getsonly updates for states while other stuff (especially API updates only go into 22.x) . We shouldn't do that for too long IMO, we saw how much effort that takes with v20.
Encouraging people to try out a branch is ofc an easy option.

@Poolitzer
Copy link
Member

I dont get the first point, what do you mean with not using new states. In conjunction with ConvHandler? I thought this is supposed to fully replace it. In that case I would like a big announcement at least (maybe also in readme or so).

We might not really need an extra release now that I think of it, just make a big fuss about it before calling it stable and doing the deprecation error warning.

@Bibo-Joshi
Copy link
MemberAuthor

I dont get the first point, what do you mean with not using new states. In conjunction with ConvHandler? I thought this is supposed to fully replace it. In that case I would like a big announcement at least (maybe also in readme or so).

Yes, this is supposed to replace CH. But, since this is acting on a different level than handlers, theconversationhandlerbot.py example will still run as before. We should throw hella-big warnings if states & CH are used in conjunction, true.
All other features that are independent of CH will also all run the same. all keys will always be in state "IDLE".

We might not really need an extra release now that I think of it, just make a big fuss about it before calling it stable and doing the deprecation error warning.

👍

@Poolitzer
Copy link
Member

Ah yes you mean in the transition period alright. Okay so lets do a release when we are happy and announce that its there and see if we get feedback, and then do a deprecation later. Perfect.

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@PoolitzerPoolitzerPoolitzer left review comments

@harshil21harshil21Awaiting requested review from harshil21

@clot27clot27Awaiting requested review from clot27

Assignees
No one assigned
Labels
🔌 enhancementpr description: enhancement🛠 refactorchange type: refactor
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

2 participants
@Bibo-Joshi@Poolitzer

[8]ページ先頭

©2009-2025 Movatter.jp