-
Notifications
You must be signed in to change notification settings - Fork 90
Make Choice states chainable #132
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.
The description of changes in the commit summary should capture the rationale behind the change in addition to what is being changed.
tests/unit/test_steps.py
Outdated
# Chain s2_choice when default_choice is already set will trigger Warning | ||
with caplog.at_level(logging.WARNING): | ||
Chain([s2_choice, s1_pass]) | ||
log_message = ( | ||
"Chaining Choice Step: Overwriting %s's current default_choice (%s) with %s" % | ||
(s2_choice.state_id, s3_pass.state_id, s1_pass.state_id) | ||
) | ||
assert log_message in caplog.text | ||
assert s2_choice.default == s1_pass | ||
assert s2_choice.next_step is None # Choice steps do not have next_step |
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'm not sure whether this is an idiomatic Python thing, but I'd extract this into a separate test:
the way I interpret the control flow, there are 2 scenarios and sets of assertions.
The 2 tests from what i can tell:
- test that choice state has a default that is established if there wasn't one because it
MUST
transition to another state - test that warning is produced when a default state is already set and a choice state is chained because the behaviour is counter-intuitive + overwrites an existing value
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.
Agreed, it makes sense to divide the test in 2!
tests/unit/test_steps.py
Outdated
"Chaining Choice Step: Overwriting %s's current default_choice (%s) with %s" % | ||
(s2_choice.state_id, s3_pass.state_id, s1_pass.state_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.
nit: might be more clear to just establish the expected string and call it expected_warning
or something similar
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.
Agreed! Made the changes in latest commit
tests/unit/test_steps.py
Outdated
"Chaining Choice Step: Overwriting %s's current default_choice (%s) with %s" % | ||
(s2_choice.state_id, s3_pass.state_id, s1_pass.state_id) | ||
) | ||
assert log_message in caplog.text |
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.
does the log include other stuff? curious why we use in
and not equality?
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, it includes the logging type (in our case WARNING) and the line(and file) where it occurred.
I can add an assert to check if "WARNING" is in caplog.text
CONTRIBUTING.md
Outdated
@@ -80,7 +80,7 @@ You should only worry about manually running any new integration tests that you | |||
|
|||
1. Create a new git branch: | |||
```shell | |||
git checkout -b my-fix-branch master | |||
git checkout -b my-fix-branch main |
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.
nit: prefer these in a separate PR to observe our guidance to focus on the change being introduced. this PR is small so it doesn't take away focus, but just something to keep in mind.
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.
Done :)
Removed the changes into another PR!
src/stepfunctions/steps/states.py
Outdated
if self.type is 'Choice': | ||
if self.default is not None: | ||
logger.warning( | ||
"Chaining Choice Step: Overwriting %s's current default_choice (%s) with %s", |
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.
"Chaining Choice Step: Overwriting %s's current default_choice (%s) with %s", | |
"Chaining Choice state: Overwriting %s's current default_choice (%s) with %s", |
I think we should be consistent with calling it a state
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.
Agreed! Done in the latest commit :)
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.
Actually the SDK does refer to "steps" with states being a type of step.
src/stepfunctions/steps/states.py
Outdated
raise ValueError('Unexpected State instance `{step}`, State type `{state_type}` does not support method `next`.'.format(step=next_step, state_type=self.type)) | ||
|
||
# By design, choice states do not have the Next field. Setting default to make it chainable. |
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.
explain the why
rather than the what
so that future contributors can gain insight.
there's some great info in the gist that @wong-a created that could be paraphrased. Might also be helpful to link to the language spec
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.
Good point!
Done!
tests/unit/test_steps.py
Outdated
@@ -372,6 +367,40 @@ def test_chaining_steps(): | |||
assert s1.next_step == s2 | |||
assert s2.next_step == s3 | |||
|
|||
|
|||
def test_chaining_choice(): |
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.
It's helpful to include the unit being tested, the inputs, and the expected outcome in the test names. This acts documentation for the expected behaviour.
def test_chaining_choice(): | |
def test_chaining_choice_sets_default_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.
Good point! Will make the change in the next commit.
assert s1_pass.next_step == s2_choice | ||
assert s2_choice.default == s3_pass | ||
assert s2_choice.next_step is None # Choice steps do not have next_step | ||
assert s3_pass.next_step is None |
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.
When writing unit tests for these modules we should consider making assertions on the generated JSON too. One of the main responsibilities is generating ASL so we should make sure it's correct.
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.
Agreed! Will include assertions in the generated JSON in the next commit.
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.
As discussed offline, there is already a unit test (test_choice_state_creation) that ensures the Choice state creation generates the expected ASL
tests/unit/test_steps.py
Outdated
assert s3_pass.next_step is None | ||
|
||
|
||
def test_chaining_choice_with_default(caplog): |
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.
def test_chaining_choice_with_default(caplog): | |
def test_chaining_choice_with_existing_default_overrides_value(caplog): |
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 agree, the suggested name makes the test objective clearer.
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.
tip: you can use the Commit suggestion button to commit suggestions in the UI
src/stepfunctions/steps/states.py
Outdated
raise ValueError('Unexpected State instance `{step}`, State type `{state_type}` does not support method `next`.'.format(step=next_step, state_type=self.type)) | ||
|
||
# By design, Choice states do not have the Next field. Their purpose is to define conditional transitions based |
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 should document the behaviour of chaining Choice states publicly. Consumers of the SDK won't be looking at the source code. Something along the lines of:
When used in a chain, the subsequent step becomes the default choice that executes if none of the specified rules match.
The docstrings in our source code become part of the public documentation and are visible in user's IDE: https://aws-step-functions-data-science-sdk.readthedocs.io/en/stable/states.html#stepfunctions.steps.states.Choice
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.
Very good point! Will make sure to update the public documentation as well.
src/stepfunctions/steps/states.py
Outdated
if self.type is 'Choice': | ||
if self.default is not None: | ||
logger.warning( | ||
"Chaining Choice state: Overwriting %s's current default_choice (%s) with %s", |
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.
nit: not a blocker but can try using f-string next time: https://www.python.org/dev/peps/pep-0498/
Got this recommendation from Shiv and think this could also help here. Same for the unit test string
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.
Good point! Using fstring will make it more readable and faster! Making changes in the next commit!
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Issue #, if available:31
Description of changes:
It was discussed in issue #31 that the non terminal Choice state behaviour was not obvious since it could not be chained. This PR allows Choice states to be chained by setting default_choice() instead of the Next field (that the Choice state does not have).
Test
Added 2 unit tests in test_steps to test:
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.