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

Fix subtle bug in listify function + simplify list munging #16904

Merged
merged 4 commits into from Oct 24, 2023

Conversation

jdavcs
Copy link
Member

@jdavcs jdavcs commented Oct 23, 2023

Subtle bug (discovered while upgrading SQLAlchemy syntax.)

The ModelManager._munge_filters method is supposed to "combine two lists into a single list" (as per docstring; which is also the right behavior). However, if one of the arguments is None and the other argument is not a list, it will return the other argument unchanged. This becomes a problem when one tries to iterate over the returned value, which is not an unlikely scenario:

filters = self._munge_filters(my_new_filter, current_filters)  # current_filters is None
stmt = select(Foo).where(*filters)  # this will break

The obvious fix is to replace the existing implementation with a one-liner reusing our existing listify util function on both arguments: return listify(listA) + listify(listB). However, this exposed a bug: if one of the arguments is an object with the special __bool__ method overridden, listify may return an empty list - which is the case with SQLAlchemy filters. A filter is a sql expression (e.g. Foo.bar == condition); so checking if not my_filter will simply apply not to the sql expression and negate it, returning True - and that 's where the code assumes the item as None and returns an empty list. Explicitly checking for None fixes this.

List munging moved to util.

Unit tests included.

How to test the changes?

(Select all options that apply)

  • I've included appropriate automated tests.
  • This is a refactoring of components with existing test coverage.
  • Instructions for manual testing are as follows:
    1. [add testing steps and prerequisites here if you didn't write automated tests covering all your changes]

License

  • I agree to license these and all my past contributions to the core galaxy codebase under the MIT license.

@github-actions github-actions bot added this to the 23.2 milestone Oct 23, 2023
@@ -1069,7 +1069,7 @@ def listify(item: Any, do_strip: bool = False) -> List:
:rtype: list
:returns: The input as a list
"""
if not item:
if item is None or item == "":
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a pretty risky change. Can you move the special casing to munge_filters ? It does change things i.e. when item is 0.

Copy link
Member

Choose a reason for hiding this comment

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

It surely is counter-intuitive that listify(0) and listify(False) return [], but as Marius says we cannot change the behaviour of this function any more.
We probably need a listify_new function that does what you were proposing here, and use that in new code / when refactoring.

@@ -1882,3 +1882,14 @@ def enum_values(enum_class):
Values are in member definition order.
"""
return [value.value for value in enum_class.__members__.values()]


def munge_lists(listA: Any, listB: Any) -> List:
Copy link
Member

@mvdbeek mvdbeek Oct 24, 2023

Choose a reason for hiding this comment

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

I don't think we want more functions in util/__init__.py, which we have to maintain indefinitely as the public api. Apart from that munge_lists is not a good name and we're not re-using this anywhere.

Copy link
Member

Choose a reason for hiding this comment

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

@jmchilton can you elaborate ?

Also, drop _munge_filters and use util function directly.
Move function to managers.base; rename.
Do not reuse listify; implement standalone function.
Add test case for tuples.
(See PR code review for discussion)
@jdavcs
Copy link
Member Author

jdavcs commented Oct 24, 2023

I've added a function to managers.base that handles just this use case (unlike the more generalized util.listify). Is this a better approach?

Copy link
Member

@mvdbeek mvdbeek left a comment

Choose a reason for hiding this comment

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

Thank you, I think this looks good.

@nsoranzo
Copy link
Member

The "Test Galaxy packages" failure is related.

Because the passed argument is not a SA filter, but our own parsed
filter, which is a tuple.
@jdavcs
Copy link
Member Author

jdavcs commented Oct 24, 2023

This function is indeed a very special case: listify wouldn't have worked here. The arguments are not SQLAlchemy expressions (as I had naively assumed), but ParsedFilter objects - our own abstraction which is itself a tuple. Caught by our unit tests - good thing we had them!

@jdavcs jdavcs merged commit 018258d into galaxyproject:dev Oct 24, 2023
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants