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

Implement $arrayToObject operator #624

Merged
merged 1 commit into from
May 24, 2020
Merged

Implement $arrayToObject operator #624

merged 1 commit into from
May 24, 2020

Conversation

hugo19941994
Copy link
Contributor

Hi!

I've implemented the arrayToObject operator for the aggregation pipeline.

I've added tests for all the edge cases I could think of:

  • Mixed array and document inside the same array
  • Extra keys in documents
  • Missing keys in document
  • Empty document
  • More than two elements in an array
  • Just one element in an array
  • Empty array

Duplicated keys are also accounted for and tested (latest found value is used, overwriting previous values), but there is a caveat in the mongo documentation for earlier versions. I'm not sure if this should be accounted for or mocking the behaviour for the latest versions is enough.

@codecov
Copy link

codecov bot commented May 6, 2020

Codecov Report

Merging #624 into develop will increase coverage by 0.05%.
The diff coverage is 100.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop     #624      +/-   ##
===========================================
+ Coverage    94.76%   94.81%   +0.05%     
===========================================
  Files           16       17       +1     
  Lines         3247     3281      +34     
===========================================
+ Hits          3077     3111      +34     
  Misses         170      170              
Impacted Files Coverage Δ
mongomock/aggregate.py 97.15% <100.00%> (+0.05%) ⬆️
mongomock/collection.py 91.63% <0.00%> (-0.06%) ⬇️
mongomock/read_concern.py 100.00% <0.00%> (ø)
mongomock/filtering.py 98.92% <0.00%> (+0.35%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d9e8d87...9e7e0e2. Read the comment docs.

@hugo19941994
Copy link
Contributor Author

I'll add some tests for those missing lines for coverage

@hugo19941994
Copy link
Contributor Author

hugo19941994 commented May 6, 2020

It looks like $arrayToObject with a null value is valid too, and returns another null. I've added this edge case too.

Copy link
Member

@pcorpet pcorpet left a comment

Choose a reason for hiding this comment

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

Great works, thanks for adding.

@@ -3065,6 +3065,36 @@ def test_aggregate_date_to_string(self):
]
self.cmp.compare.aggregate(pipeline)

def test_aggregate_array_to_object(self):
self.cmp.do.drop()
self.cmp.do.insert_one({
Copy link
Member

Choose a reason for hiding this comment

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

Please use insert_many to add all those docs at once.

}]
self.assertEqual(expect, list(actual))

with self.assertRaises(TypeError):
Copy link
Member

Choose a reason for hiding this comment

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

Here you could use a table test: create a list of items that should trigger an error and then iterate over them and do an aggregation and an asserRaises for each of them.


with self.assertRaises(TypeError):
collection.aggregate([
{'$addFields': {'items': 100}},
Copy link
Member

Choose a reason for hiding this comment

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

Add all those errors in test__mongomock using compare_exceptions

if parsed is None:
return None

if not isinstance(parsed, list):
Copy link
Member

Choose a reason for hiding this comment

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

In general were list is supported, tuple is supported as well. Can you check on pymongo and update it here please?

raise TypeError('$arrayToObject only supports arrays')

def check_doc(elements):
return all([isinstance(x, dict) and 'k' in x and 'v' in x and len(x) == 2
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need the [] here; Also you might be able to check with set equality:

return all(isinstance(x, dict) and x.keys() == {'k', 'v'} for x in elements)

for x in elements])

def check_arr(elements):
return all([isinstance(x, list) and len(x) == 2 for x in elements])
Copy link
Member

Choose a reason for hiding this comment

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

here as well, please drop the [] and allow for tuple


arr_to_obj = {}
for elements in parsed:
if isinstance(elements, list):
Copy link
Member

Choose a reason for hiding this comment

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

This one can just be built with arr_to_obj = dict(parsed)

@hugo19941994
Copy link
Contributor Author

hugo19941994 commented May 23, 2020

Thanks for the thorough review and your suggestions. I've implemented your requests and squashed my commits together. In addition to your requests I've made the following changes:

  • Implementation was further refactored
    • Used dictionary comprehension to convert {k, v} values to the expected objects
    • Dropped the two defs
    • Dropped the any and replaced by plain if/elses
  • I've replaced the TypeErrors with PyMongo's OperationFailure (so that compare_exceptions worked)
  • Added a bunch of tests to check that tuples work just as well as lists

While checking PyMongo I've noticed that following exception messages can be thrown:

$arrayToObject requires an array of size 2 arrays,found array of size: 3
$arrayToObject requires an object keys of 'k' and 'v'. Found incorrect number of keys:3
$arrayToObject requires an object with keys 'k' and 'v'. Missing either or both keys from: {v: 1, t: "t"}
$arrayToObject requires a consistent input format. Elements mustall be arrays or all be objects. Array was detected, now found: object

All of the messages can be seen in the mongo repo. My implementation doesn't check the reason for an OperationFailure in most cases and uses a generic error message. If you want I can change the code so that the same messages are used, but I wanted to check with you before adding more complexity to the code.

EDIT: It looks like I broke something with Python 2.7. I'll take a look.

Copy link
Member

@pcorpet pcorpet left a comment

Choose a reason for hiding this comment

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

Thanks for the neat changes..

  • Totally OK not to have the same error message, as long as it's the same type. So let's keep it simpler.
  • I prefer that we don't account for old versions of pymongo so your solution is best.

Please try to fix the python 2.7 version and my 2 little comments on indentation then I'll merge.

return None

if not isinstance(parsed, (list, tuple)):
raise OperationFailure('$arrayToObject requires an array input, '
Copy link
Member

Choose a reason for hiding this comment

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

Please break after OperationFailure(: that helps with cleaner indentation that does not depend on the length of the class name.

if all(isinstance(x, (list, tuple)) and len(x) == 2 for x in parsed):
return dict(parsed)

raise OperationFailure('arrays used with $arrayToObject must contain documents '
Copy link
Member

Choose a reason for hiding this comment

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

Same here, break after OperationFailure(

@hugo19941994
Copy link
Contributor Author

Python 2.7 should be fixed now. .keys() returns a list in Python 2.7 which can't be directly compared against a set (unlike in python 3).

@pcorpet pcorpet merged commit c15d173 into mongomock:develop May 24, 2020
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.

None yet

2 participants