rope icon indicating copy to clipboard operation
rope copied to clipboard

Docs for Moving class

Open williamjamir opened this issue 7 years ago • 5 comments

Hi, I want to use to move a class foo() from module A to B

I was looking at the source code and I found that the MoveGlobal would be the way to do this.

Currently, I have this structure

|   rope_script.py
|
+---A
|       a.py
|       __init__.py
|
\---B
        b.py
        __init__.py

With the content of the file A as:

class foo():
    ID = 'foo'

With the following script, I can create the MoveGlobal object and move the Class

import os
import rope.base.project
from rope.base import libutils
from rope.refactor import move

my_project = rope.base.project.Project(os.getcwd())

path_a = r'A/a.py'
path_b = r'B/b.py'

origin = libutils.path_to_resource(my_project, path_a)
destination = libutils.path_to_resource(my_project, path_b)
offset = origin, origin.read().index('foo()')

move_object = move.create_move(my_project, origin, offset )

changes = move_object.get_changes(destination)

my_project.do(changes)

So my question is:

  • Is this the correct approach?
  • Using the offset in this way would not be a "hack"?
  • There are other ways to located Class inside the resource or move this class to another module?

williamjamir avatar Jan 05 '18 12:01 williamjamir

That looks pretty much as expected, yes. The offset is the only way that rope exposes to do this-- mostly this is used by IDEs such as in ropemacs or rope-vim.

soupytwist avatar Jan 05 '18 18:01 soupytwist

So I mainly made this question because I want to move class/method around my project.

This project is substantially big, approximately 7,600 files 😬 (actually it's a project with subprojects inside of it )

My concern is that this library (rope) is taking too much time to process the changes and I was thinking that I was doing something completely wrong.

Just for a test, I moved a class named PlotWindow from one file to another. Example:

project1/source/python/project1/model/foo.py
project2/source/python/project2/model/foo.py

Then I counted the time that each "step" took to run

Steps:

  • Create the "move" object
  • Get the changes
  • Do the changes

So here is the result:

('Took: ', 77.12553733926345, 'seconds to create the object type ', <class 'rope.refactor.move.MoveGlobal'>)
('Took: ', 1165.2032418602294, ' seconds to get the changes for', 'Moving global <PlotWindow>')
('Took: ', 7727.581688521474, ' seconds to made the changes')

77.12 seconds = 1 minutes, 17 seconds 1165.20 seconds = 19 minutes, 25 seconds 7727 seconds = 02 hours, 08 minutes, 47seconds

At the end, the changes was not made as expect, at least for me =)

Before:
from project1.model.foo import PlotWindow
After
import foo.PlotWindow

So my concern is that I'm doing something terribly wrong because even the first step to create the MoveGlobal is taking too much time given that I explicitly give the full path and the index.

I made the same "Move Refactor" using PyCharm and the result was really fast.

Here the full script used:

import os
import rope.base.project
from rope.base import libutils
from rope.refactor import move
my_project = rope.base.project.Project(os.getcwd())

path_a = <Path>.py
path_b = <Path>py

origin = libutils.path_to_resource(my_project, path_a)
destination = libutils.path_to_resource(my_project, path_b)
offset = origin.read().index('PlotWindow()')

import timeit
move_object_start_time = timeit.default_timer()
move_object = move.create_move(my_project, origin, offset)
move_object_finish_time = timeit.default_timer() - move_object_start_time

print("Took: ", move_object_finish_time, 'seconds to create the object type ', type(move_object))

changes_start_time = timeit.default_timer()
changes = move_object.get_changes(destination)
changes_finish_time = timeit.default_timer() - changes_start_time

print('Took: ',changes_finish_time,' seconds to get the changes for', changes.description)

do_start_time = timeit.default_timer()
my_project.do(changes)
do_finish_time = timeit.default_timer() - do_start_time
print('Took: ',do_finish_time,' seconds to made the changes')

williamjamir avatar Jan 16 '18 20:01 williamjamir

Here is a variation on the above, where a finder is used to find the class definition that in turn is used to get the offset. Its enclosed in a pyinvoke task for easy testing.

from invoke import task
from rope.base.project import Project
from rope.refactor.occurrences import Finder

PROJECT = Project('.')

@task
def move_class(ctxt, class_name, source, target, do=False):
    """
    Move class: --class-name <> --source <module> --target <module> [--do False]
    """
    finder = Finder(PROJECT, class_name)
    source_resource = PROJECT.get_resource(source)
    target_resource = PROJECT.get_resource(target)
    class_occurrence = next(
        occ for occ in finder.find_occurrences(resource=source_resource)
        if occ.is_defined())
    mover = move.create_move(PROJECT, source_resource, class_occurrence.offset)
    changes = mover.get_changes(target_resource)
    PROJECT.do(changes)

joystein avatar Dec 19 '19 10:12 joystein

@williamjamir @joystein What is the action item from this ticket? Is there something particular which can be done for rope? Any pull request?

mcepl avatar Dec 19 '19 14:12 mcepl

@mcepl, I guess the title, albeit a little short, indicates what action is desired; adding documentation for how a class is moved. I found the comments by @williamjamir helful, but wanted a slightly more "programmatic" way of finding the class offset, so I added the above.

joystein avatar Dec 19 '19 15:12 joystein