Skip to content

Initial rough framework for plugins #952

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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft

Initial rough framework for plugins #952

wants to merge 8 commits into from

Conversation

tconley1428
Copy link
Contributor

What was changed

Why?

Checklist

  1. Closes

  2. How was this tested:

  1. Any docs updates needed?


# Iterate over interceptors in reverse building the impl
self._impl: OutboundInterceptor = _ClientImpl(self)
for interceptor in reversed(list(interceptors)):
Copy link
Member

Choose a reason for hiding this comment

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

This interceptor list now needs to be pulled off the self._config. Don't use any more parameters after on_create_client is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. I also need to go through and make a good set of tests around this sort of thing.

@@ -1184,3 +1189,46 @@ def shutdown(self) -> None:
if self.next_exception_task:
self.next_exception_task.cancel()
setattr(self.worker._bridge_worker, self.attr, self.orig_poll_call)


class MyCombinedPlugin(temporalio.client.Plugin, temporalio.worker.Plugin):

Choose a reason for hiding this comment

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

What is the value to users of this multiple inheritance approach? It seems to be a discoverability loss--knowing you can add a second base class is not a leap I'd expect most people to make.
I'm also wondering how a plugin author will register the name/version of their plugin in this scheme.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would say a few things:

  1. It does inform a user of the plugin where it is intended to be used. This could be handled via documentation, but so can the discoverability loss.
  2. It's consistent with our existing layout for Interceptors. Should there also only be one kind of intercepter which you use everywhere? So that you can discover what is interceptable. If you think so, is it worth differing our design between them since that ship has sailed.

@cretz probably has thoughts here.

Name/version aren't present, but easy to add a function(s) to the Plugin classes which has to be implemented. I'll bring this up to the team as well.

Copy link
Member

@cretz cretz Jul 11, 2025

Choose a reason for hiding this comment

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

Basically what @tconley1428 said. The main benefit is separating what you're plugging into, same as interceptors (and has consistency with interceptors on this front for those familiar). But it's not a big deal, we can make temporalio.common.Plugin that combines client and worker concerns if we want, we've just traditionally tried to separate client and worker abstractions. We don't do worker stuff outside of the worker module for example, which is a logical approach.

Choose a reason for hiding this comment

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

It does inform a user of the plugin where it is intended to be used.

This is redundantly shown by the names of the methods and the types of those methods, e.g. on_create_worker.

It's consistent with our existing layout for Interceptors

The purpose of a plugin is different from an interceptor. The whole point of a plugin is to bring together disparate things (interceptors, activities, etc) under one roof. The two purposes of this are (a) to help users easily add and (b) to give a roadmap for framework integrators to know what they can/need to do to. It's counterproductive to try to separate them out at this layer.

But it's not a big deal, we can make temporalio.common.Plugin that combines client and worker concerns if we want

👍, I would be in favor of temporalio.common.Plugin. If you want to make that multiply inherit, I don't mind if there's a good reason and as long as the samples all use the common.Plugin.

The main benefit is separating what you're plugging into

I'm not following the root reason here.

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

Successfully merging this pull request may close these issues.

3 participants