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

Statically typed notifier #6728

Merged
merged 2 commits into from
Feb 8, 2022

Conversation

kozlovsky
Copy link
Contributor

No description provided.

@kozlovsky kozlovsky force-pushed the feature/statically_typed_notifier branch 8 times, most recently from 68666cb to 6cc48d9 Compare January 17, 2022 18:55
Copy link
Contributor

@devos50 devos50 left a comment

Choose a reason for hiding this comment

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

Thanks for your work! Overall, the syntax to invoke a particular notification topic looks like this:

self.notifier[notifications.channel_discovered]({"results": results, "uuid": str(CHANNELS_VIEW_UUID)})

I like the syntax - I can see that it's less error prone and it's intuitive to use.

At the same time, looking at the old notifier again, I believe our old notifier functioned "just good enough" and I personally have not used the notifier in a very long time (maybe because I mainly focus on the GUI parts so other devs might disagree there). As such, the long-term benefit of this refactoring effort is, at least for me, unclear.

Nonetheless, I appreciate the contribution and since the PR seems to be nearly complete, I would be in favour of finishing it up and merging it 👍

src/tribler-common/tribler_common/notifier.py Outdated Show resolved Hide resolved
src/tribler-common/tribler_common/notifier.py Outdated Show resolved Hide resolved
@kozlovsky kozlovsky force-pushed the feature/statically_typed_notifier branch 3 times, most recently from 841625e to 544b980 Compare February 7, 2022 11:13
@kozlovsky
Copy link
Contributor Author

retest this please

@kozlovsky kozlovsky force-pushed the feature/statically_typed_notifier branch 2 times, most recently from 456308a to 3197f7d Compare February 7, 2022 12:28
@kozlovsky kozlovsky force-pushed the feature/statically_typed_notifier branch from 3197f7d to 107e026 Compare February 7, 2022 12:52
@kozlovsky kozlovsky changed the title [WIP] Statically typed notifier Statically typed notifier Feb 7, 2022
@kozlovsky kozlovsky marked this pull request as ready for review February 7, 2022 14:52
@kozlovsky kozlovsky requested review from a team, xoriole and devos50 and removed request for a team February 7, 2022 14:52
@kozlovsky
Copy link
Contributor Author

@devos50, thank you for the review! I simplified the logic of the Notifier and removed unnecessary methods. Also, I added the synchronous flag to the add_observer method, allowing the observer to be called immediately and not in the next iteration of the event loop. For example, it may be helpful for the new_torrent_metadata_created notification when the tag processor adds tags to a new torrent before sending the information to GUI.

A statically typed notifier should prevent bugs like #6753 fixes: all parameters to add_observer will be checked immediately at runtime as well as statically checked by IDE.

I also added the documentation to the Notifier:

    Allows communication between different Tribler modules and components.

    With Notifier, you can subscribe observer to a topic and receive notifications. The topic is a function,
    and the observer should be a callable with the same signature. Notifier is statically typed - if an observer
    has an incorrect signature or notification is called with wrong arguments, you should get a TypeError.
    PyCharm also should highlight incorrect observer registration and incorrect topic invocation.

    An example of usage:

    First, you need to create a Notifier instance. You can pass an event loop if the notifier should be able
    to process notifications asynchronously.

    >>> import asyncio
    >>> notifier = Notifier(loop=asyncio.get_event_loop())

    A topic is a function with an arbitrary signature. Usually, it has an empty body (a pass statement) but can include
    a debug code as well. It is called when notification is sent to observers.

    >>> def topic(foo: int, bar: str):
    ...     print("Notification is sent!")
    ...

    An observer should have the same signature as the topic (the return type is ignored for convenience). It may be a
    bound method of an object, in that case the `self` argument is also ignored.

    >>> def observer(foo: int, bar: str):
    ...     print("Observer called with", foo, bar)
    ...
    >>> def second_observer(foo: int, bar: str):
    ...     print("Second observer called with", foo, bar)
    ...

    To connect an observer to a specific notification, you can use the `add_observer` method. The method checks that
    the topic and the observer have the same signature.

    >>> notifier.add_observer(topic, observer)

    Observers can be registered as synchronous or asynchronous. Synchronous observers are called immediately,
    and asynchronous observers are called in the subsequent event loop iterations. By default, the observer
    is asynchronous if the notifier was initialized with an event loop. You can explicitly specify if the observer
    is synchronous or not:

    >>> notifier.add_observer(topic, second_observer, synchronous=True)

    To call observers for a specific topic in a type-safe manner, use square brackets syntax. If you are not aware
    what arguments should be used for specific topic, in IDE you can click on the topic function name and jump to the
    function signature.

    >>> notifier[topic](123, "abc")
    >>> notifier[topic](foo=123, bar="abc")

    When you invoke a notifier, all observers for the topic receive notification in the order as they were registered
    (synchronous observers first, then asynchronous).

    As an alternative, you can use the `notify` method, but without static type checks:

    >>> notifier.notify(topic, foo=123, bar="abc")

    The last way to invoke notification is by a topic function name. It can be useful when writing generic code.
    To be able to call the topic in this manner, it should have at least one observer:

    >>> notifier.notify_by_topic_name("topic", foo=123, bar="abc")

    You can also register a generic observer, receiving notifications for any topic. It will receive the topic
    as a first argument. When notification is called, generic observers are called before topic-specific observers
    in the same order as they were registered:

    >>> def generic_observer(topic, *args, **kwargs):
    ...     print("Generic observer called for", topic.__name__, "with", args, kwargs)
    ...
    >>> notifier.add_generic_observer(generic_observer)

    You can remove an observer or generic observer by calling the corresponding method:

    >>> notifier.remove_observer(observer)
    >>> notifier.remove_generic_observer(generic_observer)

    In Tribler, both Core and GUI have notifiers. Tribler uses generic observer to retranslate a subset of topics
    from Core to GUI. Core notifier is attached to the event loop and processes most topics asynchronously.
    GUI does not have an event loop, so GUI notifier processes retranslated topics synchronously. Basically, GUI
    notifier fires corresponding Qt signal for each topic.

    EventsEndpoint in Core and EventsRequestManager in GUI implement this logic of retranslation. EventsEndpoint adds
    a generic observer that listens to all topics, serializes a subset of notification calls to JSON, and sends them
    to GUI. EventRequestManager receives messages, deserializes arguments and calls `notifier.notify_by_topic_name`.

devos50
devos50 previously approved these changes Feb 7, 2022
Copy link
Contributor

@devos50 devos50 left a comment

Choose a reason for hiding this comment

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

Thanks for processing my comments! Note that I didn't do a full thorough review again (since the PR is quite big and I already looked at it earlier) but my concerns are addressed. I recommend asking another Tribler dev for a second review before merging (I think @xoriole is already requested?) 👍

@kozlovsky kozlovsky force-pushed the feature/statically_typed_notifier branch from 395a101 to 3565b57 Compare February 8, 2022 08:43
@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 8, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 12 Code Smells

No Coverage information No Coverage information
0.5% 0.5% Duplication

Copy link
Contributor

@xoriole xoriole left a comment

Choose a reason for hiding this comment

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

Nice 👍

@kozlovsky kozlovsky merged commit 1c458cd into Tribler:main Feb 8, 2022
@kozlovsky kozlovsky deleted the feature/statically_typed_notifier branch February 8, 2022 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants