web-dev-qa-db-fra.com

Mise à jour automatique Python code source (importations)

Nous refactons notre base de code.

Vieille:

from a.b import foo_method

Nouvelle:

from b.d import bar_method

Les deux méthodes (foo_method() et bar_method()) sont les mêmes. Il a juste changé le nom du paquet.

Étant donné que l'exemple ci-dessus n'est qu'un exemple de plusieurs manières qu'une méthode peut être importée, je ne pense pas qu'une simple expression régulière peut aider ici.

Comment refactuer l'importation d'un module avec un outil de ligne de commande?

Beaucoup de lignes de code source doivent être modifiées, de sorte qu'un IDE n'ajoute pas ici.

5
guettli

Dans les coulisses, les IDes ne sont pas bien plus que des éditeurs de texte avec des bouchons de fenêtres et des fichiers binaires ci-joints pour effectuer différents types d'emplois, tels que la compilation, le débogage, le code de marquage, la session, etc. Finalement, l'une de ces bibliothèques peut être utilisée pour le refacteur. Une de ces bibliothèques est Jedi, mais il y en a une qui a été spécifiquement faite pour gérer le refactoring, qui est corde .

pip3 install rope

Une solution CLI

Vous pouvez essayer d'utiliser leur API, mais comme vous avez demandé un outil de ligne de commande et il n'y en avait pas, enregistrez le fichier suivant n'importe où accessible (un dossier relatif connu de votre corbeille d'utilisateur, etc.) et le rendre exécutable chmod +x pyrename.py.

#!/usr/bin/env python3
from rope.base.project import Project
from rope.refactor.rename import Rename
from argparse import ArgumentParser

def renamodule(old, new):
    prj.do(Rename(prj, prj.find_module(old)).get_changes(new))

def renamethod(mod, old, new, instance=None):
    mod = prj.find_module(mod)
    modtxt = mod.read()
    pos, inst = -1, 0
    while True:
        pos = modtxt.find('def '+old+'(', pos+1)
        if pos < 0:
            if instance is None and prepos > 0:
                pos = prepos+4 # instance=None and only one instance found
                break
            print('found', inst, 'instances of method', old+',', ('tell which to rename by using an extra integer argument in the range 0..' if (instance is None) else 'could not use instance=')+str(inst-1))
            pos = -1
            break
        if (type(instance) is int) and inst == instance:
            pos += 4
            break # found
        if instance is None:
            if inst == 0:
                prepos = pos
            else:
                prepos = -1
        inst += 1
    if pos > 0:
        prj.do(Rename(prj, mod, pos).get_changes(new))

argparser = ArgumentParser()
#argparser.add_argument('moduleormethod', choices=['module', 'method'], help='choose between module or method')
subparsers = argparser.add_subparsers()
subparsermod = subparsers.add_parser('module', help='moduledottedpath newname')
subparsermod.add_argument('moduledottedpath', help='old module full dotted path')
subparsermod.add_argument('newname', help='new module name only')
subparsermet = subparsers.add_parser('method', help='moduledottedpath oldname newname')
subparsermet.add_argument('moduledottedpath', help='module full dotted path')
subparsermet.add_argument('oldname', help='old method name')
subparsermet.add_argument('newname', help='new method name')
subparsermet.add_argument('instance', nargs='?', help='instance count')
args = argparser.parse_args()
if 'moduledottedpath' in args:
    prj = Project('.')
    if 'oldname' not in args:
        renamodule(args.moduledottedpath, args.newname)
    else:
        renamethod(args.moduledottedpath, args.oldname, args.newname)
else:
    argparser.error('nothing to do, please choose module or method')

Créons un environnement de test avec exactement le scénario affiché dans la question (en supposant ici un utilisateur Linux):

cd /some/folder/

ls pyrename.py # we are in the same folder of the script

# creating your test project equal to the question in prj child folder:
mkdir prj; cd prj; cat << EOF >> main.py
#!/usr/bin/env python3
from a.b import foo_method

foo_method()
EOF
mkdir a; touch a/__init__.py; cat << EOF >> a/b.py
def foo_method():
    print('yesterday i was foo, tomorrow i will be bar')
EOF
chmod +x main.py

# testing:
./main.py
# yesterday i was foo, tomorrow i will be bar
cat main.py
cat a/b.py

Utilisez maintenant le script pour renommer des modules et des méthodes:

# be sure that you are in the project root folder


# rename package (here called module)
../pyrename.py module a b 
# package folder 'a' renamed to 'b' and also all references


# rename module
../pyrename.py module b.b d
# 'b.b' (previous 'a.b') renamed to 'd' and also all references also
# important - oldname is the full dotted path, new name is name only


# rename method
../pyrename.py method b.d foo_method bar_method
# 'foo_method' in package 'b.d' renamed to 'bar_method' and also all references
# important - if there are more than one occurence of 'def foo_method(' in the file,
#             it is necessary to add an extra argument telling which (zero-indexed) instance to use
#             you will be warned if multiple instances are found and you don't include this extra argument


# testing again:
./main.py
# yesterday i was foo, tomorrow i will be bar
cat main.py
cat b/d.py

Cet exemple a fait exactement quelle est la question.

Seul le renommer des modules et des méthodes a été mis en œuvre car c'est la portée de la question. Si vous avez besoin de plus, vous pouvez incrémenter le script ou créer un nouveau à partir de zéro, apprendre de leur documentation et de ce script lui-même. Pour la simplicité, nous utilisons le dossier actuel comme dossier de projet, mais vous pouvez ajouter un paramètre supplémentaire dans le script pour le rendre plus flexible.

1
brunoff