Skip to content

Commit

Permalink
✨ NEW: Transformer for removing dict and list keywords
Browse files Browse the repository at this point in the history
We recently adapted the constructors of the `Dict` and `List` nodes so
they no longer require the `dict` and `list` keywords, see:

aiidateam/aiida-core#5165

Here we add a transformer that automatically removes these keywords from
`Dict` and `List` constructors if present.
  • Loading branch information
mbercx committed Mar 10, 2022
1 parent be64a11 commit f8be454
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 7 deletions.
37 changes: 37 additions & 0 deletions .github/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: ci

on: [push, pull_request]

jobs:

tests:

runs-on: ubuntu-latest

strategy:
matrix:
python-version: ['3.8', '3.9', '3.10']

steps:
- uses: actions/checkout@v2

- name: Cache Python dependencies
uses: actions/cache@v1
with:
path: ~/.cache/pip
key: pip-${{ matrix.python-version }}-tests-${{ hashFiles('**/setup.json') }}
restore-keys:
pip-${{ matrix.python-version }}-tests

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install Python dependencies
run: |
pip install --upgrade pip setuptools wheel
pip install -e .[tests]
- name: Run pytest
run: pytest -sv tests
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
46 changes: 46 additions & 0 deletions aiida_upgrade/methods.py
Original file line number Diff line number Diff line change
@@ -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
9 changes: 4 additions & 5 deletions aiida_upgrade/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import libcst as cst

from .entry_points import FactoryCoreTransformer, FullEntryPointTransformer
from .methods import DictListNoKeywordTransformer


def migrate_path(path: Path):
Expand All @@ -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)
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ license = {file = "LICENSE"}
classifiers = ["License :: OSI Approved :: MIT License"]
dynamic = ["version", "description"]
dependencies = [
"click"
"click",
"libcst"
]

[project.urls]
Expand All @@ -20,7 +21,7 @@ Home = "https://github.com/aiidateam/aiida-upgrade"
aiida-upgrade = "aiida_upgrade.__main__:main"

[project.optional-dependencies]
testing = [
tests = [
"pytest",
]

Expand Down
30 changes: 30 additions & 0 deletions tests/test_methods.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit f8be454

Please sign in to comment.