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

Ensure correct types for QueryBuilder().dict() with multiple projections #3695

Conversation

sphuber
Copy link
Contributor

@sphuber sphuber commented Dec 20, 2019

Fixes #3679

The returned results by the backend implementation of QueryBuilder are
passed through get_aiida_entity_res to convert backend instances to
front end class instances. However, in the iterdict accumulator this
was not accounting for mappings and sequences. This would cause problems
when multiple projections were used, such as project=['*', 'id']. The
'*' notation will cause the entire node instance to be loaded, however
due to the fact that it was inside a dictionary in the response it would
not be converted.

@@ -1994,15 +1995,20 @@ def get_query(self):
self._hash = queryhelp_hash
return query

@staticmethod
def get_aiida_entity_res(value):
def get_aiida_entity_res(self, value):
Copy link
Member

Choose a reason for hiding this comment

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

could be a @classmethod, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

could be, but I think staticmethod makes more sense. Now that I fixed the problem in the correct place (thanks for catching this 👍) I think we can keep this as was

"""Convert a projected query result to front end class if it is an instance of a `BackendEntity`.

Values that are not an `BackendEntity` instance will be returned unaltered

:param value: a projected query result to convert
:return: the converted value
"""
if isinstance(value, collections.abc.Mapping):
return {key: self.get_aiida_entity_res(sub_value) for key, sub_value in value.items()}
Copy link
Member

@ltalirz ltalirz Dec 20, 2019

Choose a reason for hiding this comment

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

I wonder, should this not rather be fixed here?

@get_orm_entity.register(Mapping)
def _(backend_entity):
return {key: get_orm_entity(value) for key, value in backend_entity.items()}

Also, this code looks very similar, so I wonder why this is not already working...
Is just the list/tuple stuff missing from there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, it should be handled there but the problem is that there a TypeError can be raised if some of the elements are not node types, which is then caught by get_aiida_entity_res and it then returns the original value which is the original dictionary with unconverted values. I will fix it correctly

@sphuber sphuber force-pushed the fix_3679_query_builder_dict_multiple_projections branch 2 times, most recently from cf326e5 to e0d83cc Compare December 20, 2019 12:07
…tions

The returned results by the backend implementation of `QueryBuilder` are
passed through `get_aiida_entity_res` to convert backend instances to
front end class instances. It calls `aiida.orm.convert.get_orm_entity`
which is a singledispatch to convert all known backend types to its
corresponding front-end ORM analogue. The registered implementation for
`Mapping` contained the bug. It used a simple comprehension and did not
catch any `TypeError` that might be thrown from values that could not be
converted. This would bubble up to `get_aiida_entity_res` which would
then simply return the original value.

If the mapping contains a mixture of backend entities and normal types,
the entire converting would be undone. This was surfaced when calling
`dict` on a query builder instance with `project=['*', 'id']`. The
returned value for each match is a dictionary with one value an integer
corresponding to the `id` and the other value a backend node instance.
The integer would raise the `TypeError` in the `Mapping` converter and
since it wasn't caught, the backend node was also not converted.
@sphuber sphuber force-pushed the fix_3679_query_builder_dict_multiple_projections branch from e0d83cc to 9280fd5 Compare December 20, 2019 13:13
@ltalirz ltalirz self-requested a review December 20, 2019 14:58
Copy link
Member

@ltalirz ltalirz left a comment

Choose a reason for hiding this comment

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

thanks @sphuber - looks much better now.

seems pylint tripped over again...
can we actually fix this warning by using the proper capitalization?

@sphuber
Copy link
Contributor Author

sphuber commented Dec 20, 2019

seems pylint tripped over again...
can we actually fix this warning by using the proper capitalization?

I know, but I cannot reproduce it locally or in other PRs. I have been trying to fix it and already merged two more PRs in a desperate attempt to tackle this. It's infuriating. In any case it is not a problem iwth capitalization but just the fact that it is 32 characters instead of the "max allowed" 30.

@ltalirz
Copy link
Member

ltalirz commented Dec 20, 2019 via email

@sphuber
Copy link
Contributor Author

sphuber commented Dec 20, 2019

The last commit I did may have done it: https://github.com/aiidateam/aiida-core/runs/358302396
But there is no certainty

Edit: no dice...

@sphuber
Copy link
Contributor Author

sphuber commented Dec 20, 2019

What if we increase the max allowed to 32?

As a last defense I just snuck this in this last commit. Let's hope to all that is holy that that will do it

@sphuber sphuber merged commit dcd0ce4 into aiidateam:develop Dec 20, 2019
@sphuber sphuber deleted the fix_3679_query_builder_dict_multiple_projections branch December 20, 2019 16:43
csadorf pushed a commit that referenced this pull request Mar 5, 2020
…tions (#3695)

The returned results by the backend implementation of `QueryBuilder` are
passed through `get_aiida_entity_res` to convert backend instances to
front end class instances. It calls `aiida.orm.convert.get_orm_entity`
which is a singledispatch to convert all known backend types to its
corresponding front-end ORM analogue. The registered implementation for
`Mapping` contained the bug. It used a simple comprehension and did not
catch any `TypeError` that might be thrown from values that could not be
converted. This would bubble up to `get_aiida_entity_res` which would
then simply return the original value.

If the mapping contains a mixture of backend entities and normal types,
the entire converting would be undone. This was surfaced when calling
`dict` on a query builder instance with `project=['*', 'id']`. The
returned value for each match is a dictionary with one value an integer
corresponding to the `id` and the other value a backend node instance.
The integer would raise the `TypeError` in the `Mapping` converter and
since it wasn't caught, the backend node was also not converted.
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.

Projecting more than just * with QueryBuilder.iterdict results in BackendEntity instances being returned.
2 participants