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

feat: OR Query implementation #418

Merged
merged 5 commits into from
Mar 9, 2023
Merged

feat: OR Query implementation #418

merged 5 commits into from
Mar 9, 2023

Conversation

Mariatta
Copy link
Contributor

@Mariatta Mariatta commented Feb 8, 2023

Introduce new Filter classes:

  • PropertyFilter
  • And
  • Or

Add "filter" keyword arg to "Query.add_filter()"

UserWarning is now emitted when using "add_filter()" without keyword args

Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

@Mariatta Mariatta requested review from a team as code owners February 8, 2023 19:58
@product-auto-label product-auto-label bot added size: l Pull request size is large. api: datastore Issues related to the googleapis/python-datastore API. labels Feb 8, 2023
Copy link

@danieljbruce danieljbruce left a comment

Choose a reason for hiding this comment

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

LGTM! I am thinking of subclassing the composite filter like you did instead of Relying on factory methods like Filter.AND and Filter.OR in nodejs-datastore.

repr += f"\n\t{filter}"
return repr

def build_pb(self, container_pb=None):

Choose a reason for hiding this comment

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

@@ -209,30 +329,62 @@ def filters(self):
"""
return self._filters[:]

def add_filter(self, property_name, operator, value):
def add_filter(

Choose a reason for hiding this comment

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

The corresponding function name for this in nodejs-datastore is .filter.

operator=None,
value=None,
*,
composite_filter=None,

Choose a reason for hiding this comment

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

Why are there separate parameters for whether this is a property filter or a composite filter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking of making it explicit that we can accept two kinds of filter: a CompositeFilter or a PropertyFilter.
After our offline chat about its, I think it would be better if we accept just one parameter filter which can any descendants of the BaseFilter class.

I will make this change so that we will accept only one optional parameter (filter) instead of property_filter and composite_filter. This way, we can also remove the check below (https://github.com/googleapis/python-datastore/pull/418/files#diff-6053552b52329f41530b743e3fcb37f9e7622c7e165f751b94d08174df79ac26R442) where it raises error if user tries to pass both paramaters at the same time.

Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have replaced the two parameters so there is only a filter parameter here now.

@Mariatta Mariatta requested a review from a team as a code owner March 6, 2023 18:35
Copy link

@danieljbruce danieljbruce left a comment

Choose a reason for hiding this comment

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

LGTM

@@ -688,6 +875,19 @@ def _pb_from_query(query):
composite_filter = pb.filter.composite_filter
composite_filter.op = query_pb2.CompositeFilter.Operator.AND

for filter in query.filters:

Choose a reason for hiding this comment

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

The node.js version of this code is at https://github.com/googleapis/nodejs-datastore/blob/d854b57ed2b9b9f9ea148daedeb6d64e4d99de4d/src/entity.ts#L1252. In nodejs we use an AND composition here. This looks correct, but I was just curious about the reason why we see this internal difference.

@@ -688,6 +875,19 @@ def _pb_from_query(query):
composite_filter = pb.filter.composite_filter
composite_filter.op = query_pb2.CompositeFilter.Operator.AND

for filter in query.filters:
if isinstance(filter, BaseCompositeFilter):
Copy link
Contributor

Choose a reason for hiding this comment

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

this logic looks like it's repeated in a few places - any way we can consolidate?

Copy link
Contributor

Choose a reason for hiding this comment

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

@Mariatta This was merged without addressing this comment. Will there be a follow-up on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think you're referring to the similarity to the if/else build_pb above (lines 107-116), which gets called right after this one. The if/else is similar but the code being executed in each path is different.

property_filter.op = pb_op_enum

# Set the value to filter on based on the type.
if property_name == "__key__":
Copy link
Contributor

Choose a reason for hiding this comment

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

how come this logic is being removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is moved the the PropertyFilter constructor, which is a new datastructure that is meant as a replacement of the property_name, operator, and value variables.
See https://github.com/googleapis/python-datastore/pull/418/files/a6cfacfdd674de0cf59fd6571c05ae4d1624318e#diff-6053552b52329f41530b743e3fcb37f9e7622c7e165f751b94d08174df79ac26R70 where this gets moved to


client = datastore.Client()

.. doctest:: query-filter

>>> query = client.query(kind='Person')
>>> query = query.add_filter('name', '=', 'James')
Copy link
Contributor

Choose a reason for hiding this comment

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

what happens to this existing code? is it a warning?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have changed this to UserWarning.

@danieljbruce danieljbruce changed the title feat: OR Query implementation feat: OR Query implementation and pull filter proto building into a separate class Mar 8, 2023
@danieljbruce danieljbruce changed the title feat: OR Query implementation and pull filter proto building into a separate class feat: OR Query implementation Mar 8, 2023
Introduce new Filter classes:
- PropertyFilter
- And
- Or

Add "property_filter" and "composite_filter" keyword args to "Query.add_filter()"

DeprecationWarning is now emitted when using "add_filter()" without keyword args
@Mariatta Mariatta added the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Mar 8, 2023
@yoshi-kokoro yoshi-kokoro removed the kokoro:force-run Add this label to force Kokoro to re-run the tests. label Mar 8, 2023
return repr

def build_pb(self, container_pb=None):
"""Build the protobuf representation based on values in the Property Filter."""
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: Property -> Composite

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. thanks.

property_name, operator, value = filter
filter = PropertyFilter(property_name, operator, value)
child_pb = container_pb.filters.add().property_filter
filter.build_pb(container_pb=child_pb)
Copy link
Contributor

Choose a reason for hiding this comment

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

Tempted to say you should break line 116 (107, 110) out rather than type it 3 times. But I can also see an argument for leaving it where it is, so either works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it makes sense to move it out, I've updated it.

elif hasattr(client, "namespace"):
self._namespace = client.namespace
else:
self._project = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be self._namespace?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Fixed.

"Can't pass in both the positional arguments and 'filter' at the same time"
)

if property_name == "__key__" and not isinstance(value, Key):
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Can we put __key__ into a constant somewhere and use that instead? Less risk of accidentally mistyping the number of underscores IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea, I've created the KEY_PROPERTY_NAME constant and replaced the use of "__key__" here.

property_name, operator, value = filter
filter = PropertyFilter(property_name, operator, value)
pb_to_add = pb.filter.composite_filter.filters._pb.add().property_filter
filter.build_pb(container_pb=pb_to_add)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment I left above about the same line appearing at the end of a 3-way if branch.

assert len(pb.filters) == 3


def test_and_query_composite_filter():
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Can we say base_composite_filter here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.


assert alive_count > 0
assert appearance_count > 0
assert stark_family_count > 0
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we check for exact numbers here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, done.

Copy link
Contributor

@rwhogg rwhogg left a comment

Choose a reason for hiding this comment

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

Left some comments, but overall 🚀

@Mariatta Mariatta merged commit 3256951 into main Mar 9, 2023
@Mariatta Mariatta deleted the feat-or-query-pr branch March 9, 2023 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: datastore Issues related to the googleapis/python-datastore API. size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants