-
Notifications
You must be signed in to change notification settings - Fork 97
Access current update info with ID inside update handler #544
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
Conversation
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.
Naming works for me.
I think current_update_info
could just be update_info
still, since I dunno if current
necessarily tells you much about how it might not be present at all. I'm fine with either tho.
class UpdateInfo: | ||
"""Information about a workflow update.""" | ||
|
||
id: str |
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.
We use workflow_id
to differentiate from all the other _id
fields in Info
, should we call this update_id
? A simple id
I think works here but can change if needed.
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 Go we did ID, but update_id
is also fine.
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 think id
works best here.
Yeah I think the better justification for "current" is that it is not fixed and therefore only matters to the "current" context it's called within. |
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 one aspect of this implementation that isn't ideal -- the SDK is making use of the userspace contextvars feature and there's some abstraction leakage -- the SDK's current_update_info()
API will suddenly stop working if a user sets the context themselves in Python >= 3.11. This is unfortunate since in general our philosophy is to encourage users to use all features of the language, so we'd like them to be able to control contexts manually in tasks spawned in update handlers.
I'm hoping that we can solve this by modifiying our implementation of create_task
, something like this:
def create_task(
self,
coro: Union[Awaitable[_T], Generator[Any, None, _T]],
*,
name: Optional[str] = None,
context: Optional[contextvars.Context] = None,
) -> asyncio.Task[_T]:
# Context only supported on newer Python versions
if sys.version_info >= (3, 11):
if context:
if update_info := temporalio.workflow.current_update_info():
context.run(
lambda: temporalio.workflow._set_current_update_info(
update_info
)
)
Here's an illustration of the problem I'm suggesting we try to solve: Suppose Alice initially writes workflow code like
@workflow.update
async def my_handler(self):
asyncio.create_task(self.my_child_task_of_handler())
async def my_child_task_of_handler(self):
update_info = self.current_update_info()
Then later Bob changes it to:
asyncio.create_task(self.my_child_task_of_handler(), context=my_context)
Now update_info
has suddenly become None
in Alice's handler, and the reason won't be obvious to the users (they might guess, but confirming it requires looking at SDK implementation).
"""Information about a workflow update.""" | ||
|
||
id: str | ||
"""Update ID.""" |
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.
Stray string (should use comment, not string, it it's explaining the field).
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.
For dataclasses we have use docstrings like this in the past. For example, see https://python.temporal.io/temporalio.client.WorkflowExecution.html.
@dandavison - This is why we document it as using |
Agreed -- I'm not suggesting overriding it; I'm suggesting ensuring that the special Temporal-internal keys are always set on their context, whether it's a fresh context created via |
I don't think we should. If a user is choosing not to propagate the context (and we clearly state this is based on context var), that is their choice. This is the same for activity context. And this will be the same in other programming languages (e.g. Go contexts and .NET |
Yes, I can see two design decisions here: A
B
If we're going with B, then I wonder whether we should expose the contextvar directly for public use rather than wrapping it in a getter. @workflow.update
async def my_handler(self):
update_info = workflow.current_update_info.get() # user knows they are in a handler and hence that there is no possibility of LookupError That way, rather than having to explain the implementation to users, it follows naturally from the language that you can't expect this to work if you replace the context with one that doesn't inherit from the parent. And users can choose to allow The docs would read something like
That said, I see we do it as follows for activities:
|
I think we can go with B and not expose the mutable context var. It is better to explain to our users the implementation than give them raw access. Most users aren't going to care that it's a context var any more than most users care that activity context is (and we clearly document that too). This is the same in other languages. We won't give the Go context key to access this update info, we will make a getter that accesses it on the context for them. Same for other languages too IMO.
Yes, intentionally (and similar in other languages). |
"""Update ID.""" | ||
|
||
name: str | ||
"""Update type name.""" |
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 meaning is unclear to me. Is name the function name (unscoped by the class name) unless it's overridden with the attribute?
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.
Yes. This is also used in the decorator and the client calls. The meaning of update ID above it may be unclear to users too.
The "update name" is a Temporal concept like "workflow type" in "Info.workflow_type". Ideally the general documentation would detail this, but if necessary we can update all of the Python docs for signal/update/query name, all the workflow info stuff, and all of that if we want. But this one field isn't unique (it applies to the field above it, where "name" is used elsewhere, all the other fields we delegate to docs to describe, etc).
What was changed
temporalio.workflow.UpdateInfo
, similar totemporalio.workflow.Info
, with workflow-accessible information for an update (currentlyid
andname
)temporalio.workflow.current_update_info()
, similar totemporalio.workflow.info()
, with the current in-context update infocontextvars
so it works inside the handler and things it starts but nowhere elsecurrent_update_info()
is preferred overinfo()
because the latter is always present and there is nothing "current" about it, whereas the former can returnNone
if you're not in an updateChecklist