-
Notifications
You must be signed in to change notification settings - Fork 74
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
Restrict yaml loading in evaluate_parameters #33
Conversation
Mind adding a test here to show what this is correcting? |
I will add tests tomorrow. |
I second @sloretz request. Just for the record though, code seems to be precluding nested parameter arrays and verifying that arrays evaluated from substitutions have a single element type. |
Same, a test would be nice, it would also help convey what's being changed here. |
Sorry for the unclear PR yesterday. I updated the PR with a clearer description. |
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.
Thanks for adding the tests, it makes the expected change in behavior clearer. I find the logic difficult to follow, but that's not uncommon when parsing schema-less data like this, so in general I think this is looking good. I had only a few comments.
test_launch_ros/test/test_launch_ros/test_normalize_parameters.py
Outdated
Show resolved
Hide resolved
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.
yaml_evaluated_value: Union[List[str], List[int], List[float], List[bool]] = [ | ||
yaml.safe_load(item) for item in evaluated_value | ||
] | ||
if check_sequence_type_is_allowed(yaml_evaluated_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.
if check_sequence_type_is_allowed(yaml_evaluated_value): | |
if not check_sequence_type_is_allowed(yaml_evaluated_value): | |
raise TypeError( | |
'Expected a non-empty sequence, with items of uniform type. ' | |
'Allowed sequence item types are bool, int, float, str.' | |
) | |
evaluated_value = tuple(yaml_evaluated_value) |
That change will also raise an error in:
launch_ros/test_launch_ros/test/test_launch_ros/test_normalize_parameters.py
Lines 214 to 239 in 4efbfe5
def test_dictionary_with_dissimilar_array(): | |
orig = [{'foo': 1, 'fiz': [True, 2.0, 3]}] | |
norm = normalize_parameters(orig) | |
expected = ({'foo': 1, 'fiz': ('True', '2.0', '3')},) | |
# assert evaluate_parameters(LaunchContext(), norm) == expected | |
orig = [{'foo': 1, 'fiz': [True, 1, TextSubstitution(text='foo')]}] | |
norm = normalize_parameters(orig) | |
print(norm) | |
expected = ({'foo': 1, 'fiz': ('True', '1', 'foo')},) | |
assert evaluate_parameters(LaunchContext(), norm) == expected | |
orig = [{'foo': 1, 'fiz': [TextSubstitution(text='foo'), True, 1]}] | |
norm = normalize_parameters(orig) | |
expected = ({'foo': 1, 'fiz': ('foo', 'True', '1')},) | |
assert evaluate_parameters(LaunchContext(), norm) == expected | |
orig = [{'foo': 1, 'fiz': [True, 1, [TextSubstitution(text='foo')]]}] | |
norm = normalize_parameters(orig) | |
expected = ({'foo': 1, 'fiz': ('True', '1', 'foo')},) | |
assert evaluate_parameters(LaunchContext(), norm) == expected | |
orig = [{'foo': 1, 'fiz': [[TextSubstitution(text='foo')], True, 1]}] | |
norm = normalize_parameters(orig) | |
expected = ({'foo': 1, 'fiz': ('foo', 'True', '1')},) | |
assert evaluate_parameters(LaunchContext(), norm) == expected |
launch_ros/test_launch_ros/test/test_launch_ros/test_normalize_parameters.py
Lines 283 to 293 in 4efbfe5
def test_list_of_substitutions_with_yaml_lists(): | |
orig = [{ | |
'foo': 1, | |
'fiz': [ | |
[TextSubstitution(text="['asd', 'bsd']")], | |
[TextSubstitution(text="['asd', 'csd']")] | |
] | |
}] | |
norm = normalize_parameters(orig) | |
expected = ({'foo': 1, 'fiz': ("['asd', 'bsd']", "['asd', 'csd']")},) | |
assert evaluate_parameters(LaunchContext(), norm) == expected |
I can add complicated logic for allowing interpreting some dissimilar arrays as arrays of strings, and some others not.
But, what's that useful for?
I think that in all the cases in test_dictionary_with_dissimilar_array
, the user could write easily the parameters in other wat for getting an array of strings (if they want that). I wouldn't be so flexible as we are being now.
If we want that change, I could later simplify normalize_parameters
logic in a follow-up PR.
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.
That sounds fine to me. It's better to be strict and relax it later if someone makes a case for it, than to make it stricter later.
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.
To be clear, +1 to ratcheting up the exception more if all the cases don't throw yet, whether or not that's just this change or something additional.
expected = ({'foo': 1, 'fiz': ('True', '2.0', '3')},) | ||
# assert evaluate_parameters(LaunchContext(), norm) == expected | ||
with pytest.raises(TypeError) as exc: | ||
orig = [{'foo': 1, 'fiz': [True, 2.0, 3]}] |
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.
What happens if orig
is [{'foo': 1, 'fiz': ['True', '2.0', '3']}]
? Does that get correcly interpreted as a list of strings, or does it raise TypeError
?
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 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.
How so? Are plain dictionary values evaluated as YAML too? It doesn't look like it.
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.
Strings are converted to TextSubstitution
in normalize_parameters
before.
I don't have a better solution in mind, for avoiding the extra quotes.
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.
How so? Are plain dictionary values evaluated as YAML too? It doesn't look like it.
Thinking about this again, I stopped converting raw strings (str
) and TextSubstitution
like yaml.
The second one is needed, as str
are converted to TextSubstitution
in normalize_parameters
.
I'm happy with how the test looks now, but I have to recognize that both normalize_parameters
and evaluate_parameters
need a refactor. But I think that's ok for a follow-up.
@sloretz do you think this is ready? (after having green CI) |
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.
LGTM now! @sloretz ?
@@ -74,7 +76,7 @@ def _normalize_parameter_array_value(value: SomeParameterValue) -> ParameterValu | |||
# all were floats or ints, so return floats | |||
make_mypy_happy_float = cast(List[Union[int, float]], value) | |||
return tuple(float(e) for e in make_mypy_happy_float) | |||
elif Substitution in has_types and has_types.issubset({str, Substitution, tuple}): | |||
elif Substitution in has_types and has_types.issubset({str, Substitution}): |
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.
@ivanpauno why was tuple
removed?
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 doesn't have sense, normalize_to_list_of_substitutions
can't handle that and fails:
launch_ros/launch_ros/launch_ros/utilities/normalize_parameters.py
Lines 77 to 79 in 78469a9
elif Substitution in has_types and has_types.issubset({str, Substitution, tuple}): | |
# make a list of substitutions forming a single string | |
return tuple(normalize_to_list_of_substitutions(cast(SomeSubstitutionsType, value))) |
I added a test case for that:
launch_ros/test_launch_ros/test/test_launch_ros/test_normalize_parameters.py
Lines 95 to 101 in 821395a
orig = [{'foo': [ | |
[TextSubstitution(text='fiz'), TextSubstitution(text='buz')], | |
TextSubstitution(text='fiz') | |
]}] | |
norm = normalize_parameters(orig) | |
expected = ({'foo': ('fizbuz', 'fiz')},) | |
assert evaluate_parameters(LaunchContext(), norm) == expected |
Without removing
tuple
, it was failing.The old test cases are passing with the removal.
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
821395a
to
2672750
Compare
Follow up of #31.
The idea of this PR is to restrict what conversions from yaml are allowed, and what conversions are precluded. Also, substitutions that are later loaded as a list (from yaml), are converted to tuples (as it was done in the other cases).
I didn't want to raise an error in the conversions that aren't allowed, but I think it would be a good idea.