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

Flaky errors using "itertools.groupby" returning different result based on order so loosing data - 13.0, 14.0, 15.0 #105376

Open
moylop260 opened this issue Nov 8, 2022 · 5 comments

Comments

@moylop260
Copy link
Contributor

moylop260 commented Nov 8, 2022

Impacted versions

Tested in 15.0 but the same buggy code

Found for older versions

13.0

14.0

Steps to reproduce

Check the following code and output

from itertools import groupby


extended_order_lines = [
    [0, 0, {"order_id": (16433, "/"), "qty": 1.0}],
    [0, 0, {"order_id": (16434, "/"), "qty": 1.0}],
    [0, 0, {"order_id": (16433, "/"), "qty": 1.0}],
    [0, 0, {"order_id": (16434, "/"), "qty": 1.0}],
]

extended_order_lines_sorted = sorted(extended_order_lines, key=lambda x:x[2]['order_id'][0])

groupby_res = groupby(extended_order_lines, key=lambda x:x[2]['order_id'][0])
groupby_res_list = [(order_id, len(list(order_lines))) for order_id, order_lines in groupby_res]
print(groupby_res_list)
# The output is: [(16433, 1), (16434, 1), (16433, 1), (16434, 1)]

groupby_res_sorted = groupby(extended_order_lines_sorted, key=lambda x:x[2]['order_id'][0])
groupby_res_list_sorted = [(order_id, len(list(order_lines))) for order_id, order_lines in groupby_res_sorted]
print(groupby_res_list_sorted)
# The output is: [(16433, 2), (16434, 2)]

Current behavior

Notice using the same data but changing the order the output is different

Notice the first unordered output:

  • [(16433, 1), (16434, 1), (16433, 1), (16434, 1)]

Neither the id 16433 was not grouped nor the id 16434 (in fact the same key is duplicated)

It is expected for python since the documentation says:

But it is not expected for odoo where it is used.

The code above is based on:

Analyzing these lines of code:

for order_id, order_lines in groupby(extended_order_lines, key=lambda x:x[2]['order_id']):
    next(order for order in orders if order['id'] == order_id[0])['lines'] = list(order_lines)

If the extended_order_lines are not ordered with the key (or maybe yes, depending on your luck) so the result will be missing lines since it is assigning order_lines for the first item found for each order_id so the second item is ignored

Raising flaky errors losing data

Expected behavior

No loose data

No odoo logic calling groupby without sorting consideration

We can fix it in different ways:

Fixing current odoo state

We can generate a PR to Odoo for all the lines of code in odoo using this way, to make sure a sorted based on key generator is sent to groupby method

It is the more logical solution in the short term but what about if a future developer does the same wrong way

Deprecate iterators.groupby

We can deprecateiterators.groupby method from lint here:

In pro to use odoo.tools.groupby method:

  • odoo/odoo/tools/misc.py

    Lines 1168 to 1173 in bd366a6

    def groupby(iterable, key=None):
    """ Return a collection of pairs ``(key, elements)`` from ``iterable``. The
    ``key`` is a function computing a key value for each element. This
    function is similar to ``itertools.groupby``, but aggregates all
    elements under the same key, not only consecutive elements.
    """

Other solutions?
I have created this issue in order to discuss the best way to fix this flaky error in a global and standard way in order to avoid reproducing it in the future again and again for other pieces of code

It was so hard for us to detect, reproduced then justify the fix

Using an OPW ticket the team will request us to reproduce in runbot functionally but it is not easy since it is hard to reproduce because it is flaky

But technically is easier to realize where the issue is

We have customers losing data (e.g. pos order lines) because of this

The places calling itertools.groupby methods:

  • grep -rn " groupby(\|\.groupby(" odoo enterprise --include="*.py" --exclude-dir="test*" |grep -v "E\.groupby"

https://github.com/odoo/odoo/tree/a138875b/addons/account/wizard/account_automatic_entry_wizard.py#L221
https://github.com/odoo/odoo/tree/a138875b/addons/lunch/populate/lunch.py#L38
https://github.com/odoo/odoo/tree/a138875b/addons/point_of_sale/models/pos_order.py#L1111
https://github.com/odoo/odoo/tree/a138875b/addons/point_of_sale/models/product.py#L83
https://github.com/odoo/odoo/tree/a138875b/addons/point_of_sale/models/stock_picking.py#L124
https://github.com/odoo/odoo/tree/a138875b/addons/point_of_sale/models/stock_picking.py#L89
https://github.com/odoo/odoo/tree/a138875b/addons/pos_restaurant/models/pos_order.py#L132
https://github.com/odoo/odoo/tree/a138875b/addons/pos_restaurant/models/pos_order.py#L47
https://github.com/odoo/odoo/tree/a138875b/addons/pos_restaurant/models/pos_order.py#L94
https://github.com/odoo/odoo/tree/a138875b/addons/purchase_stock/models/stock_rule.py#L120
https://github.com/odoo/odoo/tree/a138875b/addons/purchase_stock/models/stock_rule.py#L205
https://github.com/odoo/odoo/tree/a138875b/addons/purchase/models/purchase.py#L544
https://github.com/odoo/odoo/tree/a138875b/addons/sale/models/sale_order.py#L784
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_move.py#L1058
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_move.py#L1421
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_move.py#L1446
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_move.py#L1451
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_package_level.py#L182
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_package_level.py#L186
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_picking.py#L1218
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_picking.py#L890
https://github.com/odoo/odoo/tree/a138875b/addons/stock/models/stock_picking.py#L894
https://github.com/odoo/odoo/tree/a138875b/addons/web/controllers/main.py#L1754
https://github.com/odoo/odoo/tree/a138875b/addons/website_blog/controllers/main.py#L57
https://github.com/odoo/odoo/tree/a138875b/odoo/addons/base/models/ir_model.py#L2153
https://github.com/odoo/odoo/tree/a138875b/odoo/addons/base/models/ir_model.py#L2153
https://github.com/odoo/odoo/tree/a138875b/odoo/addons/base/models/ir_translation.py#L896
https://github.com/odoo/odoo/tree/a138875b/odoo/addons/base/models/ir_translation.py#L896

https://github.com/odoo/enterprise/tree/9988b9aae/account_asset/report/account_assets_report.py#L133
https://github.com/odoo/enterprise/tree/9988b9aae/hr_holidays_gantt/models/hr_leave.py#L68
https://github.com/odoo/enterprise/tree/9988b9aae/l10n_be_reports/models/partner_vat_listing.py#L189
https://github.com/odoo/enterprise/tree/9988b9aae/l10n_cl_edi_boletas/models/l10n_cl_daily_sales_book.py#L70
https://github.com/odoo/enterprise/tree/9988b9aae/l10n_de_pos_res_cert/models/pos_order.py#L98
https://github.com/odoo/enterprise/tree/9988b9aae/l10n_in_reports/report/account_gst_report.py#L229
https://github.com/odoo/enterprise/tree/9988b9aae/l10n_lu_reports/models/account_general_ledger.py#L110
https://github.com/odoo/enterprise/tree/9988b9aae/pos_blackbox_be/models/pos_blackbox_be.py#L320
https://github.com/odoo/enterprise/tree/9988b9aae/pos_hr_l10n_be/models/hr_employee.py#L88
https://github.com/odoo/enterprise/tree/9988b9aae/sale_subscription_dashboard/models/sale_subscription.py#L280

UPDATED: I just found a blog about this matter:

@moylop260 moylop260 changed the title Flaky errors using "itertools.groupby" returning different result based on order Flaky errors using "itertools.groupby" returning different result based on order so loosing data Nov 8, 2022
@moylop260
Copy link
Contributor Author

moylop260 commented Nov 8, 2022

Hi @odony

I'm curious about your expert opinion here

Thanks in advance!

@moylop260 moylop260 changed the title Flaky errors using "itertools.groupby" returning different result based on order so loosing data Flaky errors using "itertools.groupby" returning different result based on order so loosing data - 13.0, 14.0, 15.0 Nov 8, 2022
moylop260 added a commit to vauxoo-dev/pylint-odoo that referenced this issue Nov 9, 2022
moylop260 added a commit to vauxoo-dev/pylint-odoo that referenced this issue Nov 9, 2022
moylop260 added a commit to vauxoo-dev/pylint-odoo that referenced this issue Nov 9, 2022
moylop260 added a commit to OCA/pylint-odoo that referenced this issue Nov 9, 2022
@odony
Copy link
Contributor

odony commented Nov 24, 2022

@moylop260 thanks for the nice analysis and the simple repro/demo code! :-)

I guess there's no shortcut, the good solution is to review all the cases where we've used itertools.groupby and fix them to user odoo.tool.groupby, which should be a drop-in replacement.

Then we could indeed discourage its usage with a pylint test. We can make it specifically for itertools.groupby, not other groupby calls. (/cc @flvr-odoo who is working on pylint lately ;-))

@rco-odoo any thoughts here?

@moylop260
Copy link
Contributor Author

Hi @odony

I can do the PR to test_lint if you agree

FYI I have already created a static lint for this here:

(But for odoo it could be a dynamic one since that it is running from an module with all the dependencies installed)

Let me know if you agree

@moylop260
Copy link
Contributor Author

@odony @flvr-odoo

Please, be aware about the following lint PR:

moylop260 added a commit to vauxoo-dev/odoo that referenced this issue Dec 15, 2022
moylop260 added a commit to vauxoo-dev/odoo that referenced this issue Dec 15, 2022
@voronind
Copy link

There is no error. groupby works correctly

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

No branches or pull requests

3 participants