-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Feature/4343 add ignore case to list and dictionary comparisons #4690
Feature/4343 add ignore case to list and dictionary comparisons #4690
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.
Thanks for the PR! Good work if this is your first contribution. There are pretty serious issues in the code especially related to normalizing values. I added added separate comments about that to few places, but they apply to rest of the code as well. I'll review the PR in more detail once these issues are fixed.
Thanks for the feedback @pekkaklarck. Some nice learnings and certainly good suggestions/improvements. I'm gonna work on it! |
I updated and implemented some of the feedback mentioned above. Some additional comments/questions from my part:
I also tried to uphold the PEP-8 standards, and also used pylint with an adjusted max 88 characters per line. So hopefully it's a bit closed to the standards (if not fully compliant) than before |
…e-to-liss-and-dictionary-comparisons
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 looks very good otherwise, but all keywords handling normalization separately adds lot of code even though there's a common _normalize
helper. The Normalizer
class I propose ought to remove most of that complexity.
I'm planning to create RF 6.1 rc 1 on Monday, June 5. There's not much time to get this PR merged before that, but because this change only affects Collections
and ought to be pretty safe in general, I'm willing to make an exception and merge this after the release candidate is out. The final release is targeted for Monday, June 12, so there's not too much time for that either.
Notice also that there have been some changes to Collections
after this PR was created. As the result there are some conflicts, but they ought to be easy to fix.
Let me know here or on Slack if you need any help!
Thanks @pekkaklarck. I was a bit too busy to work on this but will take a look at the feedback in the coming days! |
…ictionary-comparisons
update keywords to use new class
error messages
…o feature/4343-add-ignore-case-to-liss-and-dictionary-comparisons
…parisons' of https://github.com/MobyNL/robotframework into feature/4343-add-ignore-case-to-liss-and-dictionary-comparisons
@pekkaklarck I updated this, as I saw it had merge conflicts with the current version. And just a reminder that it's still open |
src/robot/libraries/Collections.py
Outdated
@@ -333,22 +340,26 @@ def list_should_not_contain_duplicates(self, list_, msg=None): | |||
|
|||
This keyword works with all iterables that can be converted to a list. | |||
The original iterable is never altered. | |||
|
|||
The ignore_case argument can be used to make comparison case-insensitive. | |||
See the Ignore case section for more details. It is new in Robot Framework 6.2. |
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.
Same (last comment, more in code)
Maybe it could be added to version 7.0? |
…e-to-liss-and-dictionary-comparisons
I enabled running this PR on GitHub Actions and one test failed:
That test uses In addition to fixing the issue, it's a good idea to add a new test for this library that validates that
We could also enhance the keyword to actually check the order if both dicts are |
As I commented issue #4343, I won't have time for a proper review right now (and the aforementioned bug anyway needs to be fixed before merging this), but I added the issue to RF 7.0 scope that it won't be forgotten. |
src/robot/libraries/Collections.py
Outdated
try: | ||
assert_equal(dict1[key], dict2[key], msg=f'Key {key}') | ||
assert_equal(dict1[key1], dict2[key2], msg=f'Key {key1}') |
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 implementation isn't correct and causes a failure in an unrelated suite that uses Dictionaries Should Be Equal
with dicts having items in different order. The old for key in keys
approach avoided the issue and should be restored. normalize
can then be used with each value like assert_equal(normalize(dict1[key]), ...)
.
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 fixed this, by resetting it to for key in keys, so this behaviour is fixed. Now differently ordered dictionaries also work. I also added some tests for this. I did this by change it to for key in keys:
I do however now encounter a problem at various tests because of key errors, that I haven't yet solved. Those can be solved by changing it to for key in normalize(keys):
, but this poses a new problem as keys is list-like, meaning it will always normalize, unless ignore_case=False
. I will work on this some more when I have time tomorrow
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 have now also fixed the error mentioned above in a very ugly way. I will commit it for now but put in some more time tomorrow to see if a cleaner solution is possible
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.
Right, if you have {'A': 1, 'B': 2}
and {'a': 1, 'b': 2}
, keys are equal case-insensitively, but comparing values by iterating over keys of either of the dicts and doing d1[k] == d2[k]
fails. Keys are returned by the method comparing keys, and it could possibly return a list of two-tuples containing matching keys of both of the dicts.
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.
Things get more complicated if you have {'a': 1, 'A': 2}
and {'a': 2, 'A': 1}
. Keys are equal and I guess we can consider values to match case-insensitively as well. Value comparison will fail, however, if comparison is done using key pairs [('a', 'a'), ('A', 'A')]
.
This gets even more "fun" with dicts having keys like 'abc', 'Abc', 'aBc', 'abC', 'ABc', 'AbC', 'aBC', 'ABC'
so that they have same values but with different keys. I have and idea how to handle this in ganeric manner, but I'm currently on mobile and writing code examples is too hard. I probably should test my idea first as well.
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 made an example using keys = [(key1, key2) for key1, key2 in zip(dict1.keys(), dict2.keys()) if normalize(key1) == normalize(key2)]
It works, however, now a new challenge rises when amount of keys is not equal. So, still ongoing
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.
Ah, I found it. In dictionary_should_contain_sub_dictionary
we also create a set of keys. This should return the couples as well
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 doesn't solve the problem yet for keys with double names. But I think that might be something that can't be solved. Because you don't know the actual pairs. If i create 2 dicts:
dict1 = {'abc': 3, 'AbC':4}
dict2 = {'ABC':4, 'Abc': 3}
It is now impossible to check if this is correct. I have 2 keys that are 3 and 2 that are 4, but i don't actually know if they are similar. Especially if they are also in a different order.
I think that if you have identically named keys (except for case), that you shouldn't be able to use ignore_case. So we could add an error message when multiple keys are present.
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 forgot to test it, but I believe this algorithm would solve the above problem:
- Collect all values with matching keys to lists. In the above case we'd get lists
[3, 4]
and[4, 3]
. - Validate that lists have same items.This wouldn't be that easy, because we couldn't simply sort lists (they could be unsortable) nor could we use sets (values could be unhashable).
Even though we could make the above work, I'm not sure is it worth the effort or a good idea in general. Making it an error to have multiple keys that normalize to the same value could be better.
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 have now added an error handling in _keys_should_be_equal
. I used:
len(set(normalize(dict1).keys()))!=len(dict1.keys())
I think this is valid, since normalize changes dict_like objects to a dictionary. So I can use sets too, after the normalizing
to account for differently ordered dicts Add atests for differently orderded dicts
Added Todo to cleanup code. Needs to be fixed before PR merge
I also ran the atests locally for collections and using_dict_variables that had a fail earlier. They all still pass, as well as the newly created ones. |
Now the rest has also run. Everything except for the Telnet testcases passed. But those have never worked locally. I guess I need some installation to have those working too. So unless there are changes in the OS, or python versions I expect all to pass |
Making it an error if dicts have duplicate keys after normalization is a good solution for handling cases like |
While making the small enhancements I mentioned above, I noticed I believe a proper fix requires adding separate |
As I commented #4343, this library now has some keywords that accept |
As mentioned in issue #4343, this is my first time contributing. I would love to have feedback and I'm still working on complying to coding principles