From 420e0a48d64e8dff3b6eeccda0af6713ce7279b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 18 Oct 2023 23:11:07 +0200 Subject: [PATCH] Add pairwise_longest tools --- modules/commission/commission_reporting.py | 10 ++-------- modules/purchase/purchase_reporting.py | 10 ++-------- modules/sale/sale_reporting.py | 11 ++--------- .../sale_opportunity/opportunity_reporting.py | 11 ++--------- modules/stock/stock_reporting_margin.py | 11 ++--------- trytond/CHANGELOG | 1 + trytond/doc/ref/tools/misc.rst | 8 ++++++++ trytond/trytond/tests/test_tools.py | 16 +++++++++++++--- trytond/trytond/tools/__init__.py | 7 ++++--- trytond/trytond/tools/misc.py | 8 +++++++- 10 files changed, 43 insertions(+), 50 deletions(-) diff --git a/modules/commission/commission_reporting.py b/modules/commission/commission_reporting.py index b4d6fadb18..3d82e2d3dd 100644 --- a/modules/commission/commission_reporting.py +++ b/modules/commission/commission_reporting.py @@ -1,6 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from itertools import tee, zip_longest from dateutil.relativedelta import relativedelta @@ -18,15 +17,10 @@ from trytond.modules.currency.fields import Monetary from trytond.modules.product import price_digits from trytond.pool import Pool from trytond.pyson import Eval, If +from trytond.tools import pairwise_longest from trytond.transaction import Transaction -def pairwise(iterable): - a, b = tee(iterable) - next(b, None) - return zip_longest(a, b) - - class Agent(ModelView, ModelSQL): "Commission Reporting per Agent" __name__ = 'commission.reporting.agent' @@ -121,7 +115,7 @@ class Agent(ModelView, ModelSQL): @property def time_series_all(self): delta = self._period_delta() - for ts, next_ts in pairwise(self.time_series or []): + for ts, next_ts in pairwise_longest(self.time_series or []): yield ts if delta and next_ts: date = ts.date + delta diff --git a/modules/purchase/purchase_reporting.py b/modules/purchase/purchase_reporting.py index f6e14acf77..32d77829fd 100644 --- a/modules/purchase/purchase_reporting.py +++ b/modules/purchase/purchase_reporting.py @@ -1,6 +1,5 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from itertools import tee, zip_longest try: import pygal @@ -17,15 +16,10 @@ from trytond.model import ModelSQL, ModelView, fields from trytond.modules.currency.fields import Monetary from trytond.pool import Pool from trytond.pyson import Eval, If +from trytond.tools import pairwise_longest from trytond.transaction import Transaction -def pairwise(iterable): - a, b = tee(iterable) - next(b, None) - return zip_longest(a, b) - - class Abstract(ModelSQL): company = fields.Many2One( @@ -122,7 +116,7 @@ class Abstract(ModelSQL): @property def time_series_all(self): delta = self._period_delta() - for ts, next_ts in pairwise(self.time_series or []): + for ts, next_ts in pairwise_longest(self.time_series or []): yield ts if delta and next_ts: date = ts.date + delta diff --git a/modules/sale/sale_reporting.py b/modules/sale/sale_reporting.py index c5e0d45946..bd12571baa 100644 --- a/modules/sale/sale_reporting.py +++ b/modules/sale/sale_reporting.py @@ -2,7 +2,6 @@ # this repository contains the full copyright notices and license terms. from collections import defaultdict from decimal import Decimal -from itertools import tee, zip_longest try: import pygal @@ -20,17 +19,11 @@ from trytond.model import ModelSQL, ModelView, UnionMixin, fields, sum_tree from trytond.modules.currency.fields import Monetary from trytond.pool import Pool from trytond.pyson import Eval, If -from trytond.tools import grouped_slice, reduce_ids +from trytond.tools import grouped_slice, pairwise_longest, reduce_ids from trytond.transaction import Transaction from trytond.wizard import StateAction, StateTransition, Wizard -def pairwise(iterable): - a, b = tee(iterable) - next(b, None) - return zip_longest(a, b) - - class Abstract(ModelSQL): company = fields.Many2One( @@ -190,7 +183,7 @@ class Abstract(ModelSQL): @property def time_series_all(self): delta = self._period_delta() - for ts, next_ts in pairwise(self.time_series or []): + for ts, next_ts in pairwise_longest(self.time_series or []): yield ts if delta and next_ts: date = ts.date + delta diff --git a/modules/sale_opportunity/opportunity_reporting.py b/modules/sale_opportunity/opportunity_reporting.py index e04cd8e573..bae04ee906 100644 --- a/modules/sale_opportunity/opportunity_reporting.py +++ b/modules/sale_opportunity/opportunity_reporting.py @@ -1,8 +1,6 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from itertools import tee, zip_longest - from dateutil.relativedelta import relativedelta from sql import Literal, Null, With from sql.aggregate import Count, Min, Sum @@ -19,15 +17,10 @@ from trytond.model import ModelSQL, ModelView, fields from trytond.modules.currency.fields import Monetary from trytond.pool import Pool from trytond.pyson import Eval, If +from trytond.tools import pairwise_longest from trytond.transaction import Transaction -def pairwise(iterable): - a, b = tee(iterable) - next(b, None) - return zip_longest(a, b) - - class Abstract(ModelSQL): company = fields.Many2One( @@ -201,7 +194,7 @@ class Abstract(ModelSQL): @property def time_series_all(self): delta = self._period_delta() - for ts, next_ts in pairwise(self.time_series or []): + for ts, next_ts in pairwise_longest(self.time_series or []): yield ts if delta and next_ts: date = ts.date + delta diff --git a/modules/stock/stock_reporting_margin.py b/modules/stock/stock_reporting_margin.py index 1769556794..b3bc1101d4 100644 --- a/modules/stock/stock_reporting_margin.py +++ b/modules/stock/stock_reporting_margin.py @@ -3,7 +3,6 @@ from collections import defaultdict from decimal import Decimal -from itertools import tee, zip_longest from sql import Literal, Null, With from sql.aggregate import Max, Min, Sum @@ -21,16 +20,10 @@ from trytond.model import ModelSQL, ModelView, fields, sum_tree from trytond.modules.currency.fields import Monetary from trytond.pool import Pool from trytond.pyson import Eval, If -from trytond.tools import grouped_slice, reduce_ids +from trytond.tools import grouped_slice, pairwise_longest, reduce_ids from trytond.transaction import Transaction -def pairwise(iterable): - a, b = tee(iterable) - next(b, None) - return zip_longest(a, b) - - class Abstract(ModelSQL, ModelView): company = fields.Many2One( @@ -211,7 +204,7 @@ class Abstract(ModelSQL, ModelView): @property def time_series_all(self): delta = self._period_delta() - for ts, next_ts in pairwise(self.time_series or []): + for ts, next_ts in pairwise_longest(self.time_series or []): yield ts if delta and next_ts: date = ts.date + delta diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index 1408886b2c..5f9cd4d3c2 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Add pairwise_longest tools * Add support for Python 3.12 * Support default values for autocomplete * Support default value from context diff --git a/trytond/doc/ref/tools/misc.rst b/trytond/doc/ref/tools/misc.rst index eaec07c0a1..6d627f4b46 100644 --- a/trytond/doc/ref/tools/misc.rst +++ b/trytond/doc/ref/tools/misc.rst @@ -16,6 +16,14 @@ Miscellaneous Return the path of the named directory in subdir from root directory. +.. function:: pairwise_longest(iterable) + + Return successive overlapping pairs taken from the input iterable. + + The number of 2-tuples in the output iterator will be the number of inputs + and ends with the ``None``. + It will be empty if the input iterable has fewer than two values. + .. function:: resolve(name) Resolve a dotted name to a global object. diff --git a/trytond/trytond/tests/test_tools.py b/trytond/trytond/tests/test_tools.py index 8aee2a7bd0..e1772c579b 100644 --- a/trytond/trytond/tests/test_tools.py +++ b/trytond/trytond/tests/test_tools.py @@ -13,9 +13,10 @@ import sql.operators from trytond.tools import ( decimal_, escape_wildcard, file_open, firstline, grouped_slice, - is_full_text, is_instance_method, likify, lstrip_wildcard, reduce_domain, - reduce_ids, remove_forbidden_chars, rstrip_wildcard, slugify, - sortable_values, strip_wildcard, timezone, unescape_wildcard) + is_full_text, is_instance_method, likify, lstrip_wildcard, + pairwise_longest, reduce_domain, reduce_ids, remove_forbidden_chars, + rstrip_wildcard, slugify, sortable_values, strip_wildcard, timezone, + unescape_wildcard) from trytond.tools.domain_inversion import ( canonicalize, concat, domain_inversion, eval_domain, extract_reference_models, localize_domain, merge, parse, @@ -128,6 +129,15 @@ class ToolsTestCase(unittest.TestCase): list(map(list, grouped_slice((x for x in range(10)), 5))), [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) + def test_pairwise_longest(self): + "Test pairwise_longest" + self.assertEqual( + list(pairwise_longest(range(3))), [(0, 1), (1, 2), (2, None)]) + + def test_pairwise_longest_empty(self): + "Test pairwise_longest empty" + self.assertEqual(list(pairwise_longest([])), []) + def test_is_instance_method(self): 'Test is_instance_method' diff --git a/trytond/trytond/tools/__init__.py b/trytond/trytond/tools/__init__.py index 9de5bd1203..e21820f61c 100644 --- a/trytond/trytond/tools/__init__.py +++ b/trytond/trytond/tools/__init__.py @@ -16,9 +16,9 @@ from .decimal_ import decistmt from .misc import ( escape_wildcard, file_open, find_dir, find_path, firstline, get_smtp_server, grouped_slice, is_full_text, is_instance_method, likify, - lstrip_wildcard, reduce_domain, reduce_ids, remove_forbidden_chars, - resolve, rstrip_wildcard, slugify, sortable_values, sql_pairing, - strip_wildcard, unescape_wildcard) + lstrip_wildcard, pairwise_longest, reduce_domain, reduce_ids, + remove_forbidden_chars, resolve, rstrip_wildcard, slugify, sortable_values, + sql_pairing, strip_wildcard, unescape_wildcard) class ClassProperty(property): @@ -52,6 +52,7 @@ __all__ = [ is_instance_method, likify, lstrip_wildcard, + pairwise_longest, reduce_domain, reduce_ids, remove_forbidden_chars, diff --git a/trytond/trytond/tools/misc.py b/trytond/trytond/tools/misc.py index ee83a34e66..725b17d76c 100644 --- a/trytond/trytond/tools/misc.py +++ b/trytond/trytond/tools/misc.py @@ -14,7 +14,7 @@ import warnings from array import array from collections.abc import Sized from functools import wraps -from itertools import islice +from itertools import islice, tee, zip_longest from sql import Literal from sql.conditionals import Case @@ -174,6 +174,12 @@ def grouped_slice(records, count=None): yield islice(records, i, i + count) +def pairwise_longest(iterable): + a, b = tee(iterable) + next(b, None) + return zip_longest(a, b) + + def is_instance_method(cls, method): for klass in cls.__mro__: type_ = klass.__dict__.get(method)