From b84d16fc4c151edd78e93ff42584a6c082294287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 12:11:13 +0100 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Sort=20data=20in=20insert=5Fass?= =?UTF-8?q?ert=20based=20on=20previous=20data=20(e.g.=20from=20a=20previou?= =?UTF-8?q?s=20run)=20to=20minimize=20the=20diff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devtools/pytest_plugin.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/devtools/pytest_plugin.py b/devtools/pytest_plugin.py index f80efd3..e717bfb 100644 --- a/devtools/pytest_plugin.py +++ b/devtools/pytest_plugin.py @@ -37,15 +37,41 @@ class ToReplace: insert_assert_summary: ContextVar[list[str]] = ContextVar('insert_assert_summary') -def insert_assert(value: Any) -> int: +def sort_data_from_source(source: Any, value: Any) -> Any: + if isinstance(value, dict) and isinstance(source, dict): + new_value = {} + used_keys = set() + for k, v in source.items(): + if k in value: + new_value[k] = sort_data_from_source(v, value[k]) + used_keys.add(k) + for k, v in value.items(): + if k not in used_keys: + new_value[k] = v + return new_value + elif isinstance(value, list) and isinstance(source, list): + new_value = [] + for i, v in enumerate(value): + if i < len(source): + new_value.append(sort_data_from_source(source[i], v)) + else: + new_value.append(v) + return new_value + else: + return value + + +def insert_assert(value: Any, prev: Any = None) -> int: call_frame: FrameType = sys._getframe(1) if sys.version_info < (3, 8): # pragma: no cover raise RuntimeError('insert_assert() requires Python 3.8+') - + if prev: + use_value = sort_data_from_source(prev, value) + else: use_value = value format_code = load_black() ex = Source.for_frame(call_frame).executing(call_frame) if ex.node is None: # pragma: no cover - python_code = format_code(str(custom_repr(value))) + python_code = format_code(str(custom_repr(use_value))) raise RuntimeError( f'insert_assert() was unable to find the frame from which it was called, called with:\n{python_code}' ) @@ -55,7 +81,7 @@ def insert_assert(value: Any) -> int: else: arg = ' '.join(map(str.strip, ex.source.asttokens().get_text(ast_arg).splitlines())) - python_code = format_code(f'# insert_assert({arg})\nassert {arg} == {custom_repr(value)}') + python_code = format_code(f'# insert_assert({arg})\nassert {arg} == {custom_repr(use_value)}') python_code = textwrap.indent(python_code, ex.node.col_offset * ' ') to_replace.append(ToReplace(Path(call_frame.f_code.co_filename), ex.node.lineno, ex.node.end_lineno, python_code)) From 5c40f8ac317470fe849ca16ff4deda75fe90f172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 12:12:02 +0100 Subject: [PATCH 2/8] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20insert=5Fas?= =?UTF-8?q?sert=20including=20old=20data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_insert_assert.py | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/test_insert_assert.py b/tests/test_insert_assert.py index 299cee8..b4d5154 100644 --- a/tests/test_insert_assert.py +++ b/tests/test_insert_assert.py @@ -167,3 +167,82 @@ def test_string_assert(x, insert_assert): ) captured = capsys.readouterr() assert '2 insert skipped because an assert statement on that line had already be inserted!\n' in captured.out + + +def test_insert_assert_sort_data(pytester_pretty): + os.environ.pop('CI', None) + pytester_pretty.makeconftest(config) + test_file = pytester_pretty.makepyfile( + """ +def test_dict(insert_assert): + old_data = { + "foo": 1, + "bar": [ + {"name": "Pydantic", "tags": ["validation", "json"]}, + {"name": "FastAPI", "description": "Web API framework in Python"}, + {"name": "SQLModel"}, + ], + "baz": 3, + } + new_data = { + "bar": [ + { + "description": "Data validation library", + "tags": ["validation", "json"], + "name": "Pydantic", + }, + {"name": "FastAPI", "description": "Web API framework in Python"}, + {"description": "DBs and Python", "name": "SQLModel"}, + {"name": "ARQ"}, + ], + "baz": 6, + "foo": 12, + } + insert_assert(new_data, old_data) +""" + ) + result = pytester_pretty.runpytest() + result.assert_outcomes(passed=1) + assert test_file.read_text() == ( + """ +def test_dict(insert_assert): + old_data = { + "foo": 1, + "bar": [ + {"name": "Pydantic", "tags": ["validation", "json"]}, + {"name": "FastAPI", "description": "Web API framework in Python"}, + {"name": "SQLModel"}, + ], + "baz": 3, + } + new_data = { + "bar": [ + { + "description": "Data validation library", + "tags": ["validation", "json"], + "name": "Pydantic", + }, + {"name": "FastAPI", "description": "Web API framework in Python"}, + {"description": "DBs and Python", "name": "SQLModel"}, + {"name": "ARQ"}, + ], + "baz": 6, + "foo": 12, + } + # insert_assert(new_data) + assert new_data == { + "foo": 12, + "bar": [ + { + "name": "Pydantic", + "tags": ["validation", "json"], + "description": "Data validation library", + }, + {"name": "FastAPI", "description": "Web API framework in Python"}, + {"name": "SQLModel", "description": "DBs and Python"}, + {"name": "ARQ"}, + ], + "baz": 6, + } +""" + ) \ No newline at end of file From 7d9bf195bd119b84d484383b38bd49e989d11067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 12:47:07 +0100 Subject: [PATCH 3/8] =?UTF-8?q?=E2=9C=85=20Update=20and=20simplify=20test?= =?UTF-8?q?=20to=20highlight=20how=20data=20is=20not=20changed=20unnecessa?= =?UTF-8?q?rily?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_insert_assert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_insert_assert.py b/tests/test_insert_assert.py index b4d5154..c368326 100644 --- a/tests/test_insert_assert.py +++ b/tests/test_insert_assert.py @@ -222,16 +222,16 @@ def test_dict(insert_assert): "tags": ["validation", "json"], "name": "Pydantic", }, - {"name": "FastAPI", "description": "Web API framework in Python"}, + {"description": "Web API framework in Python", "name": "FastAPI"}, {"description": "DBs and Python", "name": "SQLModel"}, {"name": "ARQ"}, ], "baz": 6, - "foo": 12, + "foo": 1, } # insert_assert(new_data) assert new_data == { - "foo": 12, + "foo": 1, "bar": [ { "name": "Pydantic", From d037a0af0eb82095e32ac790e70efcd7ce86814b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 12:52:50 +0100 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20test=20after=20local?= =?UTF-8?q?=20modifications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_insert_assert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_insert_assert.py b/tests/test_insert_assert.py index c368326..d67dd1b 100644 --- a/tests/test_insert_assert.py +++ b/tests/test_insert_assert.py @@ -191,12 +191,12 @@ def test_dict(insert_assert): "tags": ["validation", "json"], "name": "Pydantic", }, - {"name": "FastAPI", "description": "Web API framework in Python"}, + {"description": "Web API framework in Python", "name": "FastAPI"}, {"description": "DBs and Python", "name": "SQLModel"}, {"name": "ARQ"}, ], "baz": 6, - "foo": 12, + "foo": 1, } insert_assert(new_data, old_data) """ @@ -245,4 +245,4 @@ def test_dict(insert_assert): "baz": 6, } """ - ) \ No newline at end of file + ) From 7b26b878be173fa6bd9efd3763b759d390674938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 13:08:39 +0100 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20format=20in=20test=20r?= =?UTF-8?q?esult?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_insert_assert.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_insert_assert.py b/tests/test_insert_assert.py index d67dd1b..558a1a9 100644 --- a/tests/test_insert_assert.py +++ b/tests/test_insert_assert.py @@ -204,8 +204,7 @@ def test_dict(insert_assert): result = pytester_pretty.runpytest() result.assert_outcomes(passed=1) assert test_file.read_text() == ( - """ -def test_dict(insert_assert): + """def test_dict(insert_assert): old_data = { "foo": 1, "bar": [ @@ -243,6 +242,5 @@ def test_dict(insert_assert): {"name": "ARQ"}, ], "baz": 6, - } -""" + }""" ) From ebbbb97ee9818258f99c5371a52ebaf5adc18851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 13:16:19 +0100 Subject: [PATCH 6/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20format=20in=20output,?= =?UTF-8?q?=20double=20to=20single=20quotes=20and=20single=20lining?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_insert_assert.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/test_insert_assert.py b/tests/test_insert_assert.py index 558a1a9..eee994c 100644 --- a/tests/test_insert_assert.py +++ b/tests/test_insert_assert.py @@ -230,17 +230,13 @@ def test_dict(insert_assert): } # insert_assert(new_data) assert new_data == { - "foo": 1, - "bar": [ - { - "name": "Pydantic", - "tags": ["validation", "json"], - "description": "Data validation library", - }, - {"name": "FastAPI", "description": "Web API framework in Python"}, - {"name": "SQLModel", "description": "DBs and Python"}, - {"name": "ARQ"}, + 'foo': 1, + 'bar': [ + {'name': 'Pydantic', 'tags': ['validation', 'json'], 'description': 'Data validation library'}, + {'name': 'FastAPI', 'description': 'Web API framework in Python'}, + {'name': 'SQLModel', 'description': 'DBs and Python'}, + {'name': 'ARQ'}, ], - "baz": 6, + 'baz': 6, }""" ) From 1cac84c0e043f3466d823fda599b298807b7ca44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 13:19:29 +0100 Subject: [PATCH 7/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20types=20for=20sort=5Fd?= =?UTF-8?q?ata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devtools/pytest_plugin.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/devtools/pytest_plugin.py b/devtools/pytest_plugin.py index e717bfb..046fb29 100644 --- a/devtools/pytest_plugin.py +++ b/devtools/pytest_plugin.py @@ -39,24 +39,24 @@ class ToReplace: def sort_data_from_source(source: Any, value: Any) -> Any: if isinstance(value, dict) and isinstance(source, dict): - new_value = {} + new_dict = {} used_keys = set() for k, v in source.items(): if k in value: - new_value[k] = sort_data_from_source(v, value[k]) + new_dict[k] = sort_data_from_source(v, value[k]) used_keys.add(k) for k, v in value.items(): if k not in used_keys: - new_value[k] = v - return new_value + new_dict[k] = v + return new_dict elif isinstance(value, list) and isinstance(source, list): - new_value = [] + new_list: list[Any] = [] for i, v in enumerate(value): if i < len(source): - new_value.append(sort_data_from_source(source[i], v)) + new_list.append(sort_data_from_source(source[i], v)) else: - new_value.append(v) - return new_value + new_list.append(v) + return new_list else: return value From d32dd60cc35388c5d84ecde66588c7bac8974bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 26 Nov 2023 13:22:30 +0100 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=90=9B=20Fix=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devtools/pytest_plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/devtools/pytest_plugin.py b/devtools/pytest_plugin.py index 046fb29..97ae439 100644 --- a/devtools/pytest_plugin.py +++ b/devtools/pytest_plugin.py @@ -67,7 +67,8 @@ def insert_assert(value: Any, prev: Any = None) -> int: raise RuntimeError('insert_assert() requires Python 3.8+') if prev: use_value = sort_data_from_source(prev, value) - else: use_value = value + else: + use_value = value format_code = load_black() ex = Source.for_frame(call_frame).executing(call_frame) if ex.node is None: # pragma: no cover