diff --git a/README.md b/README.md index 2ed9abd..92cf65c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Currently, `aiida-upgrade` performs the following code refactoring: * Look for deprecated `aiida-core` entry points loaded by plugin factories and add the `core.` prefix, see [the corresponding section in the plugin migration guide](https://github.com/aiidateam/aiida-core/wiki/AiiDA-2.0-plugin-migration-guide#entry-points). * Similarly, find and correct full deprecated entry point strings e.g. `'aiida.data:structure'`. +* Remove `dict` and `list` keywords from the `Dict` and `List` node constructors, respectively. + See [PR #5165 on `aiida-core`](https://github.com/aiidateam/aiida-core/pull/5165), which removed the requirement of using these keywords. Migration steps that are not (yet) supported are: diff --git a/aiida_upgrade/methods.py b/aiida_upgrade/methods.py new file mode 100644 index 0000000..5f4b1c1 --- /dev/null +++ b/aiida_upgrade/methods.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Transformers for upgrading AiiDA methods.""" +import libcst as cst +from libcst import matchers + + +class DictListNoKeywordTransformer(cst.CSTTransformer): + """Remove ``dict`` and ``list`` keywords from constructors of ``Dict`` and ``List`` nodes.""" + + dict_constructor = matchers.Name("Dict") + list_constructor = matchers.Name("List") + + dict_keyword = matchers.Name("dict") + list_keyword = matchers.Name("list") + + def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.Call: + + if matchers.matches( + original_node.func, self.dict_constructor | self.list_constructor + ): + + # Empty `Dict` or `List` constructor + if len(original_node.args) == 0: + return original_node + # `Dict` or `List` constructor without keyword + elif original_node.args[0].keyword is None: + return original_node + # `Dict` or `List` constructor with `dict` or `list` keyword + elif matchers.matches( + original_node.args[0].keyword, self.dict_keyword | self.list_keyword + ): + arguments = list(updated_node.args) + arguments[0] = updated_node.args[0].with_changes( + equal=cst.MaybeSentinel.DEFAULT, keyword=None + ) + return updated_node.with_changes(args=arguments) + + return original_node diff --git a/aiida_upgrade/migrate.py b/aiida_upgrade/migrate.py index 768b432..7129ec0 100644 --- a/aiida_upgrade/migrate.py +++ b/aiida_upgrade/migrate.py @@ -3,6 +3,7 @@ import libcst as cst from .entry_points import FactoryCoreTransformer, FullEntryPointTransformer +from .methods import DictListNoKeywordTransformer def migrate_path(path: Path): @@ -16,11 +17,9 @@ def migrate_path(path: Path): with path.open("r") as handle: cst_tree = cst.parse_module(handle.read()) - factory_transformer = FactoryCoreTransformer() - cst_tree = cst_tree.visit(factory_transformer) - - fullentry_transformer = FullEntryPointTransformer() - cst_tree = cst_tree.visit(fullentry_transformer) + cst_tree = cst_tree.visit(FactoryCoreTransformer()) + cst_tree = cst_tree.visit(FullEntryPointTransformer()) + cst_tree = cst_tree.visit(DictListNoKeywordTransformer()) with path.open("w") as handle: handle.write(cst_tree.code) diff --git a/pyproject.toml b/pyproject.toml index 4abc97b..6e4f6b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,8 @@ license = {file = "LICENSE"} classifiers = ["License :: OSI Approved :: MIT License"] dynamic = ["version", "description"] dependencies = [ - "click" + "click", + "libcst" ] [project.urls] @@ -20,7 +21,7 @@ Home = "https://github.com/aiidateam/aiida-upgrade" aiida-upgrade = "aiida_upgrade.__main__:main" [project.optional-dependencies] -testing = [ +tests = [ "pytest", ] diff --git a/tests/test_methods.py b/tests/test_methods.py new file mode 100644 index 0000000..2107d8a --- /dev/null +++ b/tests/test_methods.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +########################################################################### +# Copyright (c), The AiiDA team. All rights reserved. # +# This file is part of the AiiDA code. # +# # +# The code is hosted on GitHub at https://github.com/aiidateam/aiida-core # +# For further information on the license, see the LICENSE.txt file # +# For further information please visit http://www.aiida.net # +########################################################################### +"""Tests for the transformers for upgrading AiiDA entry points.""" +import libcst as cst +import pytest + + +@pytest.mark.parametrize( + ("expression", "result"), + ( + ("Dict()", "Dict()"), + ("Dict(dict={'a': 1})", "Dict({'a': 1})"), + ("Dict(value={'a': 1})", "Dict(value={'a': 1})"), + ("List(list=[1, 2, 3])", "List([1, 2, 3])"), + ("List(value=[1, 2, 3])", "List(value=[1, 2, 3])"), + ), +) +def test_dict_list_no_keyword(expression, result): + """Test the ``DictListNoKeywordTransformer`` class.""" + from aiida_upgrade.methods import DictListNoKeywordTransformer + + cst_tree = cst.parse_module(expression) + assert cst_tree.visit(DictListNoKeywordTransformer()).code == result