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

Workflow Update #400

Merged
merged 29 commits into from
Oct 25, 2023
Merged

Workflow Update #400

merged 29 commits into from
Oct 25, 2023

Conversation

Sushisource
Copy link
Member

@Sushisource Sushisource commented Oct 13, 2023

DRAFT - Still need to finish async updates & add more tests

What was changed

Implement Workflow Update

Why?

Duh

Checklist

  1. Closes [Python] Workflow update support #336

  2. How was this tested:
    Added tests

  3. Any docs updates needed?
    Yup

"Dynamic updates do not support a vararg third param, use Sequence[RawValue]",
)
setattr(fn, "__temporal_update_definition", defn)
setattr(fn, "validator", partial(_update_validator, defn))
Copy link
Member Author

Choose a reason for hiding this comment

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

Probably this should return a class rather than doing this, so IDE docstring/hints work on validator

Copy link
Member Author

@Sushisource Sushisource Oct 17, 2023

Choose a reason for hiding this comment

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

I think this is quite nice now - we might consider changing the others to the extent it's compatible. Fewer magic attributes.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is still nicer I think, but avoids using a class decorator because of unlimited type hell.

@Sushisource Sushisource force-pushed the workflow-update branch 2 times, most recently from 488fa66 to 1da053f Compare October 16, 2023 21:49
@Sushisource Sushisource marked this pull request as ready for review October 20, 2023 18:26
@Sushisource Sushisource requested a review from a team as a code owner October 20, 2023 18:26
.dockerignore Outdated Show resolved Hide resolved
@@ -515,6 +515,12 @@ class GreetingWorkflow:
@workflow.query
def current_greeting(self) -> str:
return self._current_greeting

@workflow.update
def set_and_get_greeting(self, greeting: str) -> str:
Copy link
Member

Choose a reason for hiding this comment

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

Hrmm, I wonder if just altering update_salutation to be an update that return the previous salutation part would be enough. Also, I wonder if calling this update_greeting is descriptive enough. I don't think it matters much. But I will take the approach of return-old-thing in the .NET PR.

* `@workflow.update` - Defines a method as an update
* May both accept as input and return a value
* May be `async` or non-`async`
* May mutate workflow state, and make calls to other workflow APIs like starting activities, etc.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* May mutate workflow state, and make calls to other workflow APIs like starting activities, etc.
* May mutate workflow state, make calls to other workflow APIs like starting activities, etc.

(pedantic, can ignore)

scripts/Dockerfile Outdated Show resolved Hide resolved
temporalio/workflow.py Show resolved Hide resolved
temporalio/worker/_workflow_instance.py Outdated Show resolved Hide resolved
temporalio/worker/_workflow_instance.py Show resolved Hide resolved
temporalio/worker/_workflow_instance.py Outdated Show resolved Hide resolved
temporalio/workflow.py Outdated Show resolved Hide resolved
temporalio/workflow.py Outdated Show resolved Hide resolved
async def run_update() -> None:
command = self._add_command()
command.update_response.protocol_instance_id = job.protocol_instance_id
try:
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be helpful to place this method next to _run_top_level_workflow_function, or have them mention each other in a comment, for people seeking to understand our error handling behavior in the different handlers and in the main workflow code. (I'm assuming that for now we don't see any attractive way for them to share implementation.)

temporalio/worker/_interceptor.py Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
temporalio/client.py Outdated Show resolved Hide resolved
temporalio/worker/_workflow_instance.py Outdated Show resolved Hide resolved
@cretz
Copy link
Member

cretz commented Oct 24, 2023

Saw some things resolved/addressed but maybe not pushed yet pending core update or something. Will wait for push before re-reviewing.

Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

LGTM, mostly just naming of tracing spans and interceptor classes and similar small things. Oh and we probably want the update handle to have its return type generic/typed.

temporalio/client.py Outdated Show resolved Hide resolved
temporalio/client.py Outdated Show resolved Hide resolved
temporalio/client.py Outdated Show resolved Hide resolved
temporalio/contrib/opentelemetry.py Outdated Show resolved Hide resolved
temporalio/contrib/opentelemetry.py Show resolved Hide resolved
temporalio/contrib/opentelemetry.py Outdated Show resolved Hide resolved
temporalio/contrib/opentelemetry.py Outdated Show resolved Hide resolved
temporalio/worker/_interceptor.py Outdated Show resolved Hide resolved
temporalio/worker/_workflow_instance.py Outdated Show resolved Hide resolved
# the specific named update - we shouldn't fall back to the dynamic validator
# for some defined, named update which doesn't have a defined validator.
handler = self._instance.workflow_get_update_validator(input.update)
# Validator may not be defined
Copy link
Member

Choose a reason for hiding this comment

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

In theory this should not longer happen anymore since you check before interceptor, but fine to check again in here (just in case they did something wonky like unset it in an outer interceptor)

.dockerignore Outdated Show resolved Hide resolved
temporalio/client.py Outdated Show resolved Hide resolved
temporalio/client.py Outdated Show resolved Hide resolved
Comment on lines 3991 to 3993
outcome = self._known_result
return await _update_outcome_to_result(
outcome,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to change the _known_result variable name to _known_outcome. That name matches its type, and it's an important distinction since the outcome can be Failed, in which case there isn't a "result" in the sense that that word is used everywhere else.

(I'm not making this comment during a code review -- I'm making it after getting briefly confused by this issue while reading the Python implementation in the course of Typescript implementation, so a data point exists that this is a real confusion risk.)

Copy link
Member

@cretz cretz Oct 25, 2023

Choose a reason for hiding this comment

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

.NET did call it KnownOutcome but I don't think it matters much since this is private API. It seems ok to me to name something "known result" if it's a "known" thing for the "result" method later, but I'm ok changing it to known_outcome too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed

temporalio/client.py Outdated Show resolved Hide resolved
TimeoutError: The specified timeout was reached when waiting for the update result.
RPCError: Update result could not be fetched for some other reason.
"""
if self._known_result is not None:
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I'd prefer to write this as if self._known_result. The types establish that bool(self._known_result) iff the response has an Outcome.

temporalio/client.py Show resolved Hide resolved
temporalio/client.py Show resolved Hide resolved
*,
args: Sequence[Any] = [],
id: Optional[str] = None,
wait_for_stage: temporalio.api.enums.v1.UpdateWorkflowExecutionLifecycleStage.ValueType = temporalio.api.enums.v1.UpdateWorkflowExecutionLifecycleStage.UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_ADMITTED,
Copy link
Contributor

Choose a reason for hiding this comment

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

I understand why you defaulted this to ADMITTED (it's what's most natural when asking for a handle) and I know the API is not exposed to users, but ADMITTED is completely unimplemented server-side and there is no agreed plan for how it would be implemented yet, and no timeline for implementing it, so maybe it's a bit odd for it to be in the SDK codebase? Maybe it should be a required positional arg.

Copy link
Contributor

@dandavison dandavison left a comment

Choose a reason for hiding this comment

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

LGTM!

@Sushisource Sushisource merged commit 2c15ed3 into main Oct 25, 2023
11 checks passed
@Sushisource Sushisource deleted the workflow-update branch October 25, 2023 21:05
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.

[Python] Workflow update support
3 participants