# Rope > Rope supports the following configuration formats --- # Source: https://github.com/python-rope/rope/blob/master/docs/configuration.rst Configuration ============= Rope supports the following configuration formats 1. pyproject.toml 2. config.py 3. pytool.toml pyproject.toml -------------- Will be used if [tool.rope] is configured. .. code-block:: toml [tool.rope] split_imports = true autoimport.aliases = [ ['dt', 'datetime'], ['mp', 'multiprocessing'], ] config.py --------- You can also configure rope via a config.py in the ropefolder directory. It will be used if ``[tool.rope]`` is not present in ``pyproject.toml`` or ``pyproject.toml`` isn't present and config.py is present. .. code-block:: python3 def set_prefs(prefs): prefs["ignored_resources"] = [ "*.pyc", "*~", ".ropeproject", ".hg", ".svn", "_svn", ".git", ".tox", ".venv", "venv", ] Additionally, you can run an executable function at startup of rope. .. code-block:: python3 def project_opened(project): """This function is called after opening the project""" # Do whatever you like here! pytool.toml ----------- If neither a config.py or a pyproject.toml is present, rope will use a pytool.toml. It follows the exact same syntax as ``pyproject.toml``. - Mac OS X: ``~/Library/Application Support/pytool.toml``. - Unix: ``~/.config/pytool.toml``` or in $XDG_CONFIG_HOME, if defined - Windows: ``C:\Users\\AppData\Local\pytool.toml`` Options ------- .. autopytoolconfigtable:: rope.base.prefs.Prefs autoimport.* Options -------------------- .. autopytoolconfigtable:: rope.base.prefs.AutoimportPrefs imports.* Options ---------------- .. autopytoolconfigtable:: rope.base.prefs.ImportPrefs Old Configuration File ---------------------- This is a sample config.py. While this config.py works and all options here should be supported, the above documentation reflects the recommended way to do configuration in the latest version of rope. .. literalinclude:: default_config.py :language: python3 --- # Source: https://github.com/python-rope/rope/blob/master/docs/contributing.rst ====================== Contributing to Rope ====================== Getting Involved! ================= Rope's main goal is being a good refactoring tool for python. It also provides some IDE helpers. If you would like to contribute, you're welcome to! How to Help Rope? ================= Rope's development happens in `python-rope's Github`_. Use `python-rope's Github Issue Tracker`_ to discuss development-related issues: * Send bug reports and request features * Submit patches for bugs or new features Use `python-rope's Github Discussion`_ for other discussions, such as: * Help using rope * Help integrating rope with text editors/tools * Discuss your ideas * Engage with the rope community .. _`python-rope's Github`: https://github.com/python-rope/rope .. _`python-rope's Github Issue Tracker`: https://github.com/python-rope/rope/issues .. _`python-rope's Github Discussion`: https://github.com/python-rope/rope/discussions Wish List ========= You are welcome to make pull requests in `python-rope's Github Issue Tracker`_. Here is a list of suggestions. Issues ------ If this is your first time contributing in rope and you don't know where to start, tickets labeled `good first issue`_ is a good place start. The `unresolved issues list`_ in Github is the latest todo list. There is also a rather outdated list in :ref:`dev/issues:Rope Issues`. There is a section called "unresolved issues"; it contains almost every kind of task. This file will need some cleanup, thoughts, and discussions. Pickup whichever you are most interested in. If you have ideas or questions about them, don't hesitate to create a Github ticket for it. .. _`good first issue`: https://github.com/python-rope/rope/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 .. _`unresolved issues list`: https://github.com/python-rope/rope/issues Write Text Editors or IDE plugins for Rope ------------------------------------------ See pylsp-rope_, ropemacs_, ropevim_, eric4_. .. _pylsp-rope: https://github.com/python-rope/pylsp-rope/ .. _ropemacs: https://github.com/python-rope/ropemacs/ .. _ropevim: https://github.com/python-rope/ropevim/ .. _eric4: http://eric-ide.python-projects.org/ Rope Structure ============== Rope package structure: * `rope.base`: the base part of rope * `rope.refactor`: refactorings and tools used in them * `rope.contrib`: IDE helpers Have a look at ``__init__.py`` of these packages or :ref:`library:Using Rope As A Library` for more information. There's also some really good `tour of Rope's codebase`_ by Austin Bingham (author of `Traad`_). The first 10 minutes of the video talked about Rope in general, the rest are more specific to Traad. .. _tour of Rope's codebase: https://youtu.be/NvV5OrVk24c .. _traad: https://github.com/abingham/traad/ Source Repository ================= Rope uses GitHub_. The repository exists at `python-rope/rope`_. Setting up for local development ================================ #. Clone repository: ``git clone https://github.com/python-rope/rope.git`` #. Create a virtualenv: ``python -m venv rope-venv`` #. Activate the virtualenv #. Install the project into the venv: ``pip install -e '.[doc,dev]'`` Submitting pull requests ======================== Pull requests are welcome. Follow the instructions on GitHub_ on how to setup Git and fork the `python-rope/rope`_ repository. Once your changes are ready, send a `pull request`_ for review. Programming Style ----------------- * Follow `black codestyle`_ * Follow :PEP:`8`. * Use four spaces for indentation. * Include good unit-tests when appropriate. * Rope test suite should pass after patching. .. _`black codestyle`: https://github.com/psf/black Testing ------- Rope uses `pytest`_. To run the test:: pytest -v Many of rope's tests are still written using ``unittest.TestCase`` style, but running the test suite using vanilla ``unittest`` is no longer supported. Make sure to have complete test suite passing and add new tests for the changes you are providing with each new submission. All required packages for development could be installed with:: pip install -e ".[dev]" .. _GitHub: http://github.com/ .. _`python-rope/rope`: https://github.com/python-rope/rope .. _`pull request`: https://help.github.com/articles/using-pull-requests .. _`pytest`: https://pytest.org/ .. _gha-cache-key: Updating gha-cache-key.txt -------------------------- ``gha-cache-key.txt`` file is used as cache-key for Github Action to cache pip packages. Refer to `PR #650`_ to see how it works. .. _`PR #650`: https://github.com/python-rope/rope/pull/650 To re-generate the cache key, run this command: .. code-block:: sh $ pip-compile --extra dev --generate-hashes -o gha-cache-key.txt $ git add gha-cache-key.txt $ git commit --- # Source: https://github.com/python-rope/rope/blob/master/docs/dev/issues.rst ============= Rope Issues ============= NOTE: this file is severely outdated and is no longer actively used by rope to keep track of issues. It is kept only for historical purpose. Use the `Issue Tracker`_ instead .. _`Issue Tracker`: https://github.com/python-rope/rope/issues Unresolved Issues ================= * purging out less accurate callinfos when better ones appear? * using properties without calling its get? * global variable inlines * transform and extension modules * merging extract and usefunction * caching instances of PyObject * moving a group of elements together * temps might be read after body in usefunction or extract * usefunction and function returns * usefunction on methods * extracted functions should be inserted before using class bodies * adding "referenced later" wildcard argument to restructurings? * adding "change references" wildcard argument to restructurings? * ideas for more custom wildcards * custom wildcards and recursive patterns * custom restructuring wildcard patterns and replacements * not reimporting back imports after moving * importing compressed objectdb/history data? * not applying all commenting mechanisms always in codeassist * fixing try blocks before current line in code_assist * better tests for patchedast * import actions with more that one phase and filtering problems * handle long imports should work on filtered imports unconditionally? * extracting subexpressions; look at `extracttest` for more info * unignored files that are not under version control * inline fails when there is an arg mismatch * evaluate function parameter defaults in staticoi? * saving diffs instead of old contents in ChangeContents? * handling tuple parameters * extract class * analyzing function decorators * generate ... and implicit interfaces * generate method and class hierarchies * lambdas as functions; consider their parameters * renaming similarly named variables * handling the return type of ``yield`` keyword * not writing unchanged objectdb and history? To Be Reviewed ============== * review patchedast; make it faster * lots of estimations in codeanalyze in WordRangeFinder * review objectdb modules * how concluded data are held for star imports Insert Before In Restructurings =============================== Consider a restructuring like this:: pattern: ${a} if ${b} else ${c} goal: replacement before: if ${b}:\n replacement = ${a}\nelse:\n replacement = ${c} Memory Management ================= These are the places in which rope spends most of the memory it consumes: * PyCore: for storing PyModules * ObjectInfo: for storing object information * History: for storing changes We should measure the amount of memory each of them use to make decisions. Custom Restructuring Wildcards ============================== There is a need to add more custom wildcards in restructuring patterns. But adding all such needs to `similarfinder` module makes it really complex. So I think adding the ability to extend them is useful. Sometimes wildcards can be customized. For instance one might want to match the function calls only if ``p1`` is passed in the arguments. They can be specified in wildcard arguments. Since matched wildcards can appear in the goal pattern, each wildcard should have a corresponding replacement wildcard. Each replacement might be customized in each place it appears; for instance ``${mycall:-p1}`` might mean to remove ``p1`` argument. Wildcard Format --------------- All wildcards should appear as ``${name}``. The type of wildcards and their parameters can be specified using the ``args`` argument of ``Restructuring()``. Ideas: * Maybe we can put checks inside args, too:: pattern: ${project:type=rope.base.project.Project}.pycore But what should be done when a variable appears twice:: pattern: ${a:type=__builtin__.int} + ${a} Examples -------- .. ... --- # Source: https://github.com/python-rope/rope/blob/master/docs/index.rst .. rope documentation master file, created by sphinx-quickstart on Sat May 21 18:16:44 2022. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to rope's documentation! ================================ .. toctree:: :maxdepth: 2 :caption: Contents: overview rope library configuration contributing release-process dev/issues Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` --- # Source: https://github.com/python-rope/rope/blob/master/docs/library.rst ========================= Using Rope As A Library ========================= If you need other features, send a feature request. Have a look at :ref:`contributing:Contributing to Rope`. .. contents:: Table of Contents Quick Start =========== This section will help you get started as soon as possible. Making A Project ---------------- The first thing you should do is make a project: .. code-block:: python import rope.base.project myproject = rope.base.project.Project('/path/to/myproject') It's good to know that: * A project is a folder in the file-system. * It can contain anything. * Rope searches for python modules and packages inside a project when needed. * Refactorings only change files and folders inside the project that has been passed to them. * Out of project modules that are imported from a module inside a project are handled but never changed by refactorings. * Rope makes a rope folder inside projects. By default the name of this folder is ``.ropeproject``, but that can be changed using the constructor's ``ropefolder`` parameter. Passing ``None`` prevents rope from making this folder. * Rope uses the ``.ropeproject`` folder for things like saving object information and loading project configurations. * Project preferences can be configured by passing options to the constructor or in ``.ropeproject/config.py``. See the default ``config.py``, ``rope.base.default_config`` module, for more information. * All configurations that are available in the ``config.py`` file can be specified as keyword parameters to the ``Project`` constructor. These parameters override the ones in the ``config.py`` file. * Each project has a set of ignored resource patterns. You can use it to tell rope to ignore files and folders matching certain patterns. * The ``.ropeproject`` folder can be safely copied in other clones of a project if you don't want to lose your objectdb and history. Library Utilities ----------------- The `rope.base.libutils`_ module provides tools that make using rope as a library easier. We'll talk more about this module later. What Are These `Resource`\s? ---------------------------- In rope, files and folders in a project are accessed through ``rope.base.resources.Resource`` objects. It has two subclasses ``File`` and ``Folder``. What we care about is that refactorings and ``Change``\s (we'll talk about them later) use resources. There are two options for creating a ``Resource`` for a path in a project. The first approach uses the ``Project.get_resource()`` method. .. code-block:: python from rope.base import project myresource = myproject.get_resource('/path/to/resource') However, it's preferable to use the ``libutils.path_to_resource()`` function, because it's more flexible and offers a unified way to create resources. It takes a ``project`` and ``path`` as parameters with an optional ``type``. The ``type`` parameter, with values ``file`` or ``folder``, can create a resource for an object that doesn't exist yet. .. code-block:: python from rope.base import libutils myresource = libutils.path_to_resource(myproject, '/path/to/resource') Consider we have a resource. How can we know anything about it? The answer is to use its ``path`` and ``real_path`` attributes. ``Resource.real_path`` is the absolute path of the resource in the file-system. The ``Resource.path`` attribute contains the address of a resource relative to the project's root. Performing Refactorings ----------------------- As a short example of performing refactorings, we'll show how to extract a variable from a file. First we need the ``Resource`` object that points to a file in a project: .. code-block:: python resource = libutils.path_to_resource(myproject, '/path/to/my/module.py') Now we can make our Refactoring class: .. code-block:: python from rope.refactor.extract import ExtractVariable extractor = ExtractVariable(myproject, resource, start, end) Where ``start`` and ``end`` are the offsets of the region to extract in ``resource``. Be careful when calculating the offsets. DOS line-endings and multi-byte characters are considered to be one character. This is actually easier for IDEs, since most GUI libraries handle those cases for you. Next, the IDE ask the user to configure refactoring options, like specifying the name of the extracted variable. After that, we can calculate the changes: .. code-block:: python changes = extractor.get_changes('extracted_variable') Each refactoring returns a ``rope.base.change.Change`` object that holds the changes it made. Calculating those changes can be time consuming. See the `rope.base.taskhandle.TaskHandle`_ section for measuring its progress or interrupting it. Previewing And Performing Changes --------------------------------- As mentioned in the last section each refactoring returns a ``rope.base.change.Change`` object. Now how can we know what it contains and how to perform it? *Previewing*: You can use ``changes.get_description()`` to get a preview. It is useful when you don't care much about the format. Otherwise you can use the ``changes`` object directly. See the documentation in ``rope.base.change`` module. *Performing*: The easiest way for performing the refactoring is to use the `Project.do()`_ method: .. code-block:: python myproject.do(changes) If you want to perform the changes yourself, you have two options. Note that the main reason for performing the changes manually is handling version control systems that are not supported by rope. 1. The first approach is to use `rope.base.fscommands`_ (see `Writing A FileSystemCommands`_). The changes can be performed as before using `Project.do()`_. 2. The second approach is to perform the changes manually based on the returned ``changes`` object (again see the documentation in ``rope.base.change`` module). If this approach is used you cannot undo the refactoring using ``project.history.undo()``. *Updating Open Buffers In IDEs*: Usually editors need to reload the files changed by rope. You can use ``Change.get_changed_resources()`` to get the list of resources that need to be reloaded. Validating The Project ---------------------- When using rope as a library, you probably change the files in it in parallel (for example in IDEs). To force rope to invalidate cached information about resources that have been removed or changed outside rope, you should call the `Project.validate()`_ method. You can pass a resource to this method. For example: .. code-block:: python myproject.validate(resource) This validates all files and directories in resource. Call this function every time you want use rope (i.e., before performing refactorings). Performing Static Object Analysis --------------------------------- One of the greatest strengths of rope is its Static Object Analysis (SOA). It analyzes function calls and assignments to collect the types of objects passed to the function. Rope uses the collected data to infer the type of function parameters, return values, and the objects stored in built-in containers. The function ``rope.base.libutils.analyze_modules()`` performs SOA on all modules in the project. It is recommended that you call this function occasionally, and especially before performing large refactorings. Note that analyzing all modules of a project may take a long time. If you have ``automatic_soa`` set, which instructs rope to analyze the changed scopes of modules, then you should report the changes by calling ``rope.base.libutils.report_change()`` when saving files, as follows: .. code-block:: python # Save the new contents. old_contents = resource.read() resource.write(new_contents) # Inform rope about the change. libutils.report_change(myproject, path, old_contents) Note, however, that the use of ``automatic_soa`` is discouraged, because it may slow down saving considerably. Closing The Project ------------------- `Project.close()`_ closes a project's open resources. Always call this function when you don't need a project anymore: .. code-block:: python myproject.close() ``rope.base.libutils`` ====================== The ``rope.base.libutils`` module contains functions that make life easier for building refactoring tools. In some cases, the functions offer a unified way to access or create objects. You're encouraged to use ``rope.base.libutils`` functions whenever possible, because the APIs here may not be as volatile as class methods. ``libutils.analyze_module()`` ------------------------------ Perform static object analysis on a Python file in the project. Note, this may be a very time consuming task. .. code-block:: python libutils.analyze_module(myproject, resource) ``libutils.analyze_modules()`` ------------------------------ Perform static object analysis on all Python files in the project. Note that it might take a long time to finish. .. code-block:: python libutils.analyze_modules(myproject) ``libutils.get_string_module()`` -------------------------------- Returns a ``rope.base.pyobjects.PyModule`` object for the code string. An optional ``resource`` argument can be specified for the resource this code is associated with. If ``force_errors`` is ``True``, then ``rope.base.exceptions.ModuleSyntaxError`` is raised when the code has syntax errors. Otherwise, syntax errors are silently ignored. Note that ``force_errors`` overrides the ``ignore_syntax_errors`` project configuration flag. .. code-block:: python pymodule = libutils.get_string_module(myproject, source) ``libutils.get_string_scope()`` ------------------------------- Get the ``rope.base.pyscopes.GlobalScope`` object for the code string. This is the outermost scope of the code encompassing the whole module. .. code-block:: python scope = libutils.get_string_scope(myproject, source) ``libutils.is_python_file()`` ----------------------------- Returns ``True`` if the resource is a Python file. .. code-block:: python libutils.is_python_file(myproject, resource) ``libutils.modname()`` ---------------------- Retrieves the dotted path string to the module that contains that given resource. .. code-block:: python # If resource is 'path/to/resource.py' relative to the project's root # directory, this returns the string: 'path.to.resource'. module_name = libutils.modname(resource) ``libutils.path_relative_to_project_root()`` -------------------------------------------- Retrieve the path relative to the project's root directory. .. code-block:: python # Get the path relative to the project's root directory. relpath = libutils.relative(myproject.address, path) ``libutils.path_to_resource()`` ------------------------------- Get the resource --- a file or folder --- at the given path. An optional ``type`` argument can be used if the resource doesn't yet exist. The values for ``type`` are the strings ``'file'`` or ``'folder'``. .. code-block:: python # Resource for an existing file. myfile = libutils.path_to_resource(myproject, '/path/to/file.py') # Resource for a non-existing folder. new_folder = libutils.path_to_resource(myproject, '/path/to/folder', type='folder') ``rope.base.project.Project`` ============================= You can create a project by: .. code-block:: python project = Project(root_address) Where the ``root_address`` is the root folder of your project. A project has some useful attributes. ``Project.address`` is the address of the root folder of a project. ``Project.root`` is a ``Folder`` object that points to that folder. `Project.do()` -------------- Used to commit changes returned by refactorings: .. code-block:: python project.do(changes) `Project.history` ----------------- A ``rope.base.history.History`` object. You can use its ``undo`` and ``redo`` methods for undoing or redoing changes. Note that you can use this only if you have committed your changes using rope. `Project.validate()` -------------------- When using rope as a library, you will probably change the files in that project in parallel (for example in IDEs). To force rope to validate cached information about resources that have been removed or changed outside rope, you should call ``Project.validate()``. You should pass a resource to this method. For example: .. code-block:: python project.validate(project.root) This validates all files and directories in the project and clears the cache of all recorded changes. `Project.close()` ----------------- Closes a project's open resources. Always call this function when you don't need a project anymore. Currently it closes the files used for storing object information and project history. Because some parts of these files are in memory for efficiency, not closing a project might put them in an inconsistent state. `rope.base.fscommands` ====================== The ``rope.base.fscommands`` module implements basic file system operations that rope needs to perform. The main reason for the existence of this module is supporting version control systems. Have a look at ``FileSystemCommands`` and ``SubversionCommands`` in the same module. If you need other version control systems you can write a new class that provides this interface. ``rope.base.project.Project`` accepts an ``fscommands`` argument. You can use this argument to force rope to use your new class. ``.ropeproject`` Folder ======================= Since version ``0.5``, rope makes a ``.ropeproject`` folder in the project by default for saving project configurations and data. The name of this folder is passed to the constructor if you want to change that. You can force rope not to make such a folder by passing ``None``. If such a folder exists, rope loads the ``config.py`` file in that folder. It might also use it for storing object information and history. `rope.base.pycore.PyCore` ========================= Provides useful methods for managing python modules and packages. Each project has a ``PyCore`` that can be accessed using the ``Project.pycore`` attribute. ``PyCore.run_module()`` runs a resource. When running, it collects type information to do dynamic object inference. For this reason modules run much slower. Also ``Pycore.analyze_module()`` collects object information for a module. The collected information can be used to enhance rope's static object inference. `rope.base.taskhandle.TaskHandle` ================================= A TaskHandle can be used for stopping and monitoring the progress of time consuming tasks, like some refactorings. The ``Project.do()`` and ``Refactoring.get_changes()`` methods for most refactorings take a keyword parameter called ``task_handle``. You can pass a ``TaskHandle`` object to them. A ``TaskHandle`` can be used for interrupting or observing a task. Always pass ``task_handle`` as keyword argument. It will always be the last argument, and new arguments of the refactoring are added before it. A task might consist of a few ``JobSet``\s. Each ``JobSet`` performs a few jobs. For instance calculating the changes for renaming a method in a class hierarchy has two job sets: one to find the classes for constructing the class hierarchy and another to change the occurrences. The ``TaskHandle.current_jobset()`` returns the most recent ``JobSet`` or ``None`` if none has been started. You can use the methods of ``JobSet`` for obtaining information about the current job. So you might want to do something like: .. code-block:: python import rope.base.taskhandle handle = rope.base.taskhandle.TaskHandle("Test Task") def update_progress(): jobset = handle.current_jobsets() if jobset: text = '' # getting current job set name if jobset.get_name() is not None: text += jobset.get_name() # getting active job name if jobset.get_active_job_name() is not None: text += ' : ' + jobset.get_active_job_name() # adding done percent percent = jobset.get_percent_done() if percent is not None: text += ' ... %s percent done' % percent print(text) handle.add_observer(update_progress) changes = renamer.get_changes('new_name', task_handle=handle) Also you can use something like this for stopping the task: .. code-block:: python def stop(): handle.stop() After calling ``stop()``, the thread that is executing the task will be interrupted by a ``rope.base.exceptions.InterruptedTaskError`` exception. Refactorings ============ Have a look at ``rope.refactor`` package and its sub-modules. For example for performing a move refactoring you can create an object representing this operation (which will be an instance of e.g. `MoveMethod`, `MoveModule`, ...) like this: .. code-block:: python from rope.refactor.move import create_move mover = create_move(project, resource, offset) Where ``resource`` and ``offset`` is the location to perform the refactoring. Then you can commit the changes by it using the ``get_changes()`` method: .. code-block:: python project.do(mover.get_changes(destination)) Where the ``destination`` module/package is the destination resource for move refactoring. Other refactorings classes have a similar interface. List Of Refactorings -------------------- Here is the list of refactorings rope provides. (Note that this list might be out of date.) For more information about these refactoring see pydocs in their modules and the unit-tests in the ``ropetest/refactor/`` folder. * ``rope.refactor.rename``: Rename something in the project. See the example below. * ``rope.refactor.move``: Move a python element in the project. * ``rope.refactor.restructure``: Restructure code. See the example below. * ``rope.refactor.extract``: Extract methods/variables. * ``rope.refactor.inline``: Inline occurrences of a method/variable/parameter. * ``rope.refactor.usefunction``: Try to use a function wherever possible. * ``rope.refactor.method_object``: Transform a function or a method to a method object. * ``rope.refactor.change_signature``: Change the signature of a function/method. * ``rope.refactor.introduce_factory``: Introduce a factory for a class and changes all constructors to use it. * ``rope.refactor.introduce_parameter``: Introduce a parameter in a function. * ``rope.refactor.encapsulate_field``: Generate a getter/setter for a field and changes its occurrences to use them. * ``rope.refactor.localtofield``: Change a local variable to field. * ``rope.refactor.topackage``: Transform a module to a package with the same name. * ``rope.refactor.importutils``: Perform actions like organize imports. Refactoring Resources Parameter ------------------------------- Some refactorings, restructure and find occurrences accept an argument called ``resources``. If it is a list of `File`\s, all other resources in the project are ignored and the refactoring only analyzes them. If it is ``None`` all python modules in the project will be analyzed. Using this parameter, IDEs can let the user limit the files on which a refactoring should be applied. Examples ======== Rename ------ Using rename refactoring: .. code-block:: python # Creating a project >>> from rope.base.project import Project >>> project = Project('.') # Working with files to create a module >>> mod1 = project.root.create_file('mod1.py') >>> mod1.write('a_var = 10\n') # Alternatively you can use `generate` module. # Creating modules and packages using `generate` module >>> from rope.contrib import generate >>> pkg = generate.create_package(project, 'pkg') >>> mod2 = generate.create_module(project, 'mod2', pkg) >>> mod2.write('import mod1\nprint(mod1.a_var)\n') # We can use `Project.find_module` for finding modules, too >>> assert mod2 == project.find_module('pkg.mod2') # Performing rename refactoring on `mod1.a_var` >>> from rope.refactor.rename import Rename >>> changes = Rename(project, mod1, 1).get_changes('new_var') >>> project.do(changes) >>> mod1.read() u'new_var = 10\n' >>> mod2.read() u'import mod1\nprint(mod1.new_var)\n' # Undoing rename refactoring >>> project.history.undo() ... >>> mod1.read() u'a_var = 10\n' >>> mod2.read() u'import mod1\nprint(mod1.a_var)\n' # Cleaning up >>> pkg.remove() >>> mod1.remove() >>> project.close() Restructuring ------------- The example for replacing occurrences of our ``pow`` function to ``**`` operator (see ref:`overview:Restructurings`): .. code-block:: python # Setting up the project >>> from rope.base.project import Project >>> project = Project('.') >>> mod1 = project.root.create_file('mod1.py') >>> mod1.write('def pow(x, y):\n result = 1\n' ... ' for i in range(y):\n result *= x\n' ... ' return result\n') >>> mod2 = project.root.create_file('mod2.py') >>> mod2.write('import mod1\nprint(mod1.pow(2, 3))\n') >>> from rope.refactor import restructure >>> pattern = '${pow_func}(${param1}, ${param2})' >>> goal = '${param1} ** ${param2}' >>> args = {'pow_func': 'name=mod1.pow'} >>> restructuring = restructure.Restructure(project, pattern, goal, args) >>> project.do(restructuring.get_changes()) >>> mod2.read() u'import mod1\nprint(2 ** 3)\n' # Cleaning up >>> mod1.remove() >>> mod2.remove() >>> project.close() See code documentation and test suites for more information. Other Topics ============ Writing A `FileSystemCommands` ------------------------------ The ``get_changes()`` method of refactoring classes return a ``rope.base.change.Change`` object. You perform these changes by calling ``Project.do()``. But as explained above some IDEs need to perform the changes themselves. Every change to the file-system in rope is committed using an object that provides a ``rope.base.fscommands.FileSystemCommands`` interface. As explained above in `rope.base.fscommands`_ section, rope uses this interface to handle different VCSs. You can implement your own fscommands object: .. code-block:: python class MyFileSystemCommands(object): def create_file(self, path): """Create a new file""" # ... def create_folder(self, path): """Create a new folder""" # ... def move(self, path, new_location): """Move resource at `path` to `new_location`""" # ... def remove(self, path): """Remove resource""" # ... def write(self, path, data): """Write `data` to file at `path`""" # ... def read(self, path): """Read `data` from file at `path`""" # ... And you can create a project like this: .. code-block:: python my_fscommands = MyFileSystemCommands() project = rope.base.project.Project('~/myproject', fscommands=my_fscommands) `rope.contrib.codeassist` ------------------------- The ``rope.contrib`` package contains modules that use rope base parts and provide useful features. ``rope.contrib.codeassist`` module can be used in IDEs: .. code-block:: python from rope.ide import codeassist # Get the proposals; you might want to pass a Resource proposals = codeassist.code_assist(project, source_code, offset) # Sorting proposals; for changing the order see pydoc proposals = codeassist.sorted_proposals(proposals) # Where to insert the completions starting_offset = codeassist.starting_offset(source_code, offset) # Applying a proposal proposal = proposals[x] replacement = proposal.name new_source_code = (source_code[:starting_offset] + replacement + source_code[offset:]) ``maxfixes`` parameter of ``code_assist`` decides how many syntax errors to fix. The default value is one. For instance: .. code-block:: python def f(): g(my^ myvariable = None def g(p): invalid syntax ... will report ``myvariable``, only if ``maxfixes`` is greater than 1. ``later_locals``, if ``True``, forces rope to propose names that are defined later in current scope. It is ``True`` by default. For instance: .. code-block:: python def f(): my^ myvariable = None will not report ``myvariable``, if ``later_locals`` is ``False``. See pydocs and source code for more information (other functions in this module might be interesting, too; like ``get_doc``, ``get_definition_location``). `rope.contrib.findit` --------------------- ``findit`` module provides ``find_occurrences()`` for finding occurrences of a name. Also the ``find_implementations()`` function finds the places in which a method is overridden. `rope.contrib.autoimport` ------------------------- This module can be used to find the modules that provide a name. IDEs can use this module to auto-import names. ``AutoImport.get_modules()`` returns the list of modules with the given global name. ``AutoImport.import_assist()`` tries to find the modules that have a global name that starts with the given prefix. There are currently two implementations of autoimport in rope, a deprecated implementation that uses pickle-based storage (rope.contrib.autoimport.pickle.AutoImport) and a new, experimental one that uses sqlite3 database (rope.contrib.autoimport.sqlite.AutoImport). New and existing integrations should migrate to the sqlite3 storage as the pickle-based autoimport will be removed in the future. `rope.contrib.autoimport.sqlite` -------------------------------- Currently, the sqlite3-based only stores autoimport cache in an in-memory sqlite3 database, you can make it write the import cache to persistent storage by passing memory=False to AutoImport constructor. This default will change in the future, if you want to always store the autoimport cache in-memory, then you need to explicitly pass memory=True. It must be closed when done with the ``AutoImport.close()`` method. AutoImport can search for a name from both modules and statements you can import from them. .. code-block:: python from rope.base.project import Project from rope.contrib.autoimport import AutoImport project = Project("/path/to/project") autoimport = AutoImport(project, memory=False) autoimport.generate_resource_cache() # Generates a cache of the local modules, from the project you're working on autoimport.generate_modules_cache() # Generates a cache of external modules print(autoimport.search("Dict")) autoimport.close() project.close() It provides two new search methods: - search_full() - returns a list of mostly unsorted tuples. This has itemkind and source information. - search() - simpler wrapper around search_full with a basic sorting algorithm Cross-Project Refactorings -------------------------- ``rope.refactor.multiproject`` can be used to perform a refactoring across multiple projects. Usually refactorings have a main project. That is the project that contains the definition of the changing python name. Other projects depend on the main one, and the uses of the changed name in them should be updated. Each refactoring changes only one project (the project passed to its constructor). But we can use the ``MultiProjectRefactoring`` proxy to perform a refactoring on other projects, too. First we need to create a multi-project refactoring constructor. As an example consider that we want to perform a rename refactoring: .. code-block:: python from rope.refactor import multiproject, rename CrossRename = multiproject.MultiProjectRefactoring(rename.Rename, projects) Here ``projects`` is the list of dependent projects. It does not include the main project. The first argument is the refactoring class (such as ``Rename``) or factory function (like ``create_move``). Next we can construct the refactoring: .. code-block:: python renamer = CrossRename(project, resource, offset) We create the rename refactoring as we do for normal refactoings. Note that ``project`` is the main project. As mentioned above, other projects use the main project. Rope automatically adds the main project to the python path of other projects. Finally we can calculate the changes. But instead of calling ``get_changes()`` (which returns main project changes only), we can call ``get_all_changes()`` with the same arguments. It returns a list of ``(project, changes)`` tuples. You can perform them manually by calling ``project.do(changes)`` for each tuple, or use ``multiproject.perform()``: .. code-block:: python project_and_changes = renamer.get_all_changes('newname') multiproject.perform(project_and_changes) --- # Source: https://github.com/python-rope/rope/blob/master/docs/overview.rst =============== Rope Overview =============== The purpose of this file is to give an overview of some of rope's features. It is incomplete. And some of the features shown here are old and do not show what rope can do in extremes. So if you really want to feel the power of rope try its features and see its unit tests. This file is more suitable for the users. Developers who plan to use rope as a library might find :ref:`library:Using Rope As A Library` more useful. .. contents:: Table of Contents ``.ropeproject`` Folder ======================= Rope uses a folder inside projects for holding project configuration and data. Its default name is ``.ropeproject``, but it can be changed (you can even tell rope not to create this folder). Currently it is used for things such as: * There is a ``config.py`` file in this folder in which you can change project configurations. Have look at the default ``config.py`` file (is created when it does not exist) for more information. * It can be used for saving project history, so that the next time you open the project you can undo past changes. * It can be used for saving object information to help rope object inference. * It can be used for saving global names cache which is used in auto-import. You can change what to save and what not to in the ``config.py`` file. Key bindings ============ Rope is a library that is used in many IDE and Text Editors to perform refactoring on Python code. This page documents the details of the refactoring operations but you would need consult the documentation for your IDE/Text Editor client integration for the specific key bindings that are used by those IDE/Text Editors. Refactorings ============ This section shows some random refactorings that you can perform using rope. Renaming Attributes ------------------- Consider we have: .. code-block:: python class AClass(object): def __init__(self): self.an_attr = 1 def a_method(self, arg): print(self.an_attr, arg) a_var = AClass() a_var.a_method(a_var.an_attr) After renaming ``an_attr`` to ``new_attr`` and ``a_method`` to ``new_method`` we'll have: .. code-block:: python class AClass(object): def __init__(self): self.new_attr = 1 def new_method(self, arg): print(self.new_attr, arg) a_var = AClass() a_var.new_method(a_var.new_attr) Renaming Function Keyword Parameters ------------------------------------ On: .. code-block:: python def a_func(a_param): print(a_param) a_func(a_param=10) a_func(10) performing rename refactoring on any occurrence of ``a_param`` will result in: .. code-block:: python def a_func(new_param): print(new_param) a_func(new_param=10) a_func(10) Renaming modules ---------------- Consider the project tree is something like:: root/ mod1.py mod2.py ``mod1.py`` contains: .. code-block:: python import mod2 from mod2 import AClass mod2.a_func() a_var = AClass() After performing rename refactoring one of the ``mod2`` occurrences in `mod1` we'll get: .. code-block:: python import newmod from newmod import AClass newmod.a_func() a_var = AClass() and the new project tree would be:: root/ mod1.py newmod.py Renaming Occurrences In Strings And Comments -------------------------------------------- You can tell rope to rename all occurrences of a name in comments and strings. This can be done by passing ``docs=True`` to `Rename.get_changes()` method. Rope renames names in comments and strings only where the name is visible. For example in: .. code-block:: python def f(): a_var = 1 # INFO: I'm printing `a_var` print('a_var = %s' % a_var) # f prints a_var after we rename the `a_var` local variable in `f()` to `new_var` we would get: .. code-block:: python def f(): new_var = 1 # INFO: I'm printing `new_var` print('new_var = %s' % new_var) # f prints a_var This makes it safe to assume that this option does not perform wrong renames most of the time. This also changes occurrences inside evaluated strings: .. code-block:: python def func(): print('func() called') eval('func()') After renaming ``func`` to ``newfunc`` we should have: .. code-block:: python def newfunc(): print('newfunc() called') eval('newfunc()') Rename When Unsure ------------------ This option tells rope to rename when it doesn't know whether it is an exact match or not. For example after renaming `C.a_func` when the 'rename when unsure' option is set in: .. code-block:: python class C(object): def a_func(self): pass def a_func(arg): arg.a_func() C().a_func() we would have: .. code-block:: python class C(object): def new_func(self): pass def a_func(arg): arg.new_func() C().new_func() Note that the global ``a_func`` was not renamed because we are sure that it is not a match. But when using this option there might be some unexpected renames. So only use this option when the name is almost unique and is not defined in other places. Move Method Refactoring ----------------------- It happens when you perform move refactoring on a method of a class. In this refactoring, a method of a class is moved to the class of one of its attributes. The old method will call the new method. If you want to change all of the occurrences of the old method to use the new method you can inline it afterwards. For instance if you perform move method on ``a_method`` in: .. code-block:: python class A(object): pass class B(object): def __init__(self): self.attr = A() def a_method(self): pass b = B() b.a_method() You will be asked for the destination field and the name of the new method. If you use ``attr`` and ``new_method`` in these fields and press enter, you'll have: .. code-block:: python class A(object): def new_method(self): pass class B(object): def __init__(self): self.attr = A() def a_method(self): return self.attr.new_method() b = B() b.a_method() Now if you want to change the occurrences of ``B.a_method()`` to use ``A.new_method()``, you can inline ``B.a_method()``: .. code-block:: python class A(object): def new_method(self): pass class B(object): def __init__(self): self.attr = A() b = B() b.attr.new_method() Moving Fields ------------- Rope does not have a separate refactoring for moving fields. Rope's refactorings are very flexible, though. You can use the rename refactoring to move fields. For instance: .. code-block:: python class A(object): pass class B(object): def __init__(self): self.a = A() self.attr = 1 b = B() print(b.attr) consider we want to move ``attr`` to ``A``. We can do that by renaming ``attr`` to ``a.attr``: .. code-block:: python class A(object): pass class B(object): def __init__(self): self.a = A() self.a.attr = 1 b = B() print(b.a.attr) You can move the definition of ``attr`` manually. Moving Global Classes/Functions/Variables ----------------------------------------- You can move global classes/function/variables to another module by using the Move refactoring on a global object: For instance, in this refactoring, if you are moving ``twice()`` to ``pkg1.mod2``: .. code-block:: python # pkg1/mod1.py def twice(a): return a * 2 print(twice(4)) .. code-block:: python # pkg1/mod3.py import pkg1.mod1 pkg1.mod1.twice(13) When asked for the destination module, put in ``pkg1.mod2``. Rope will update all the imports. .. code-block:: python # pkg1/mod1.py import pkg1.mod2 print(pkg1.mod2.twice(4)) .. code-block:: python # pkg1/mod2.py def twice(a): return a * 2 .. code-block:: python # pkg1/mod3.py import pkg1.mod2 pkg1.mod2.twice(13) Extract Method -------------- In these examples ``${region_start}`` and ``${region_end}`` show the selected region for extraction: .. code-block:: python def a_func(): a = 1 b = 2 * a c = ${region_start}a * 2 + b * 3${region_end} After performing extract method we'll have: .. code-block:: python def a_func(): a = 1 b = 2 * a c = new_func(a, b) def new_func(a, b): return a * 2 + b * 3 For multi-line extractions if we have: .. code-block:: python def a_func(): a = 1 ${region_start}b = 2 * a c = a * 2 + b * 3${region_end} print(b, c) After performing extract method we'll have: .. code-block:: python def a_func(): a = 1 b, c = new_func(a) print(b, c) def new_func(a): b = 2 * a c = a * 2 + b * 3 return b, c Extracting Similar Expressions/Statements ----------------------------------------- When performing extract method or local variable refactorings you can tell rope to extract similar expressions/statements. For instance in: .. code-block:: python if True: x = 2 * 3 else: x = 2 * 3 + 1 Extracting ``2 * 3`` will result in: .. code-block:: python six = 2 * 3 if True: x = six else: x = six + 1 Extract Regular Method into staticmethod/classmethod ---------------------------------------------------- If you prefix the extracted method name with `@` or `$`, the generated method will be created as a `classmethod` and `staticmethod` respectively. For instance in: .. code-block:: python class A(object): def f(self, a): b = a * 2 if you select ``a * 2`` for method extraction and name the method ``@new_method``, you'll get: .. code-block:: python class A(object): def f(self, a): b = A.twice(a) @classmethod def new_method(cls, a): return a * 2 Similarly, you can prefix the name with `$` to create a staticmethod instead. Extract Method In staticmethods/classmethods -------------------------------------------- The extract method refactoring has been enhanced to handle static and class methods better. For instance in: .. code-block:: python class A(object): @staticmethod def f(a): b = a * 2 if you extract ``a * 2`` as a method you'll get: .. code-block:: python class A(object): @staticmethod def f(a): b = A.twice(a) @staticmethod def twice(a): return a * 2 Inline Method Refactoring ------------------------- Inline method refactoring can add imports when necessary. For instance consider ``mod1.py`` is: .. code-block:: python import sys class C(object): pass def do_something(): print(sys.version) return C() and ``mod2.py`` is: .. code-block:: python import mod1 c = mod1.do_something() After inlining ``do_something``, ``mod2.py`` would be: .. code-block:: python import mod1 import sys print(sys.version) c = mod1.C() Rope can inline methods, too: .. code-block:: python class C(object): var = 1 def f(self, p): result = self.var + pn return result c = C() x = c.f(1) After inlining ``C.f()``, we'll have: .. code-block:: python class C(object): var = 1 c = C() result = c.var + pn x = result As another example we will inline a ``classmethod``: .. code-block:: python class C(object): @classmethod def say_hello(cls, name): return 'Saying hello to %s from %s' % (name, cls.__name__) hello = C.say_hello('Rope') Inlining ``say_hello`` will result in: .. code-block:: python class C(object): pass hello = 'Saying hello to %s from %s' % ('Rope', C.__name__) Inlining Parameters ------------------- ``rope.refactor.inline.create_inline()`` creates an ``InlineParameter`` object when performed on a parameter. It passes the default value of the parameter wherever its function is called without passing it. For instance in: .. code-block:: python def f(p1=1, p2=1): pass f(3) f() f(3, 4) after inlining p2 parameter will have: .. code-block:: python def f(p1=1, p2=2): pass f(3, 2) f(p2=2) f(3, 4) Use Function Refactoring ------------------------ It tries to find the places in which a function can be used and changes the code to call it instead. For instance if mod1 is: .. code-block:: python def square(p): return p ** 2 my_var = 3 ** 2 and mod2 is: .. code-block:: python another_var = 4 ** 2 if we perform "use function" on square function, mod1 will be: .. code-block:: python def square(p): return p ** 2 my_var = square(3) and mod2 will be: .. code-block:: python import mod1 another_var = mod1.square(4) Automatic Default Insertion In Change Signature ----------------------------------------------- The ``rope.refactor.change_signature.ArgumentReorderer`` signature changer takes a parameter called ``autodef``. If not ``None``, its value is used whenever rope needs to insert a default for a parameter (that happens when an argument without default is moved after another that has a default value). For instance in: .. code-block:: python def f(p1, p2=2): pass if we reorder using: .. code-block:: python changers = [ArgumentReorderer([1, 0], autodef='1')] will result in: .. code-block:: python def f(p2=2, p1=1): pass Sorting Imports --------------- Organize imports sorts imports, too. It does that according to :PEP:`8`:: [__future__ imports] [standard imports] [third-party imports] [project imports] [the rest of module] Handling Long Imports --------------------- ``Handle long imports`` command tries to make long imports look better by transforming ``import pkg1.pkg2.pkg3.pkg4.mod1`` to ``from pkg1.pkg2.pkg3.pkg4 import mod1``. Long imports can be identified either by having lots of dots or being very long. The default configuration considers imported modules with more than 2 dots or with more than 27 characters to be long. Stoppable Refactorings ---------------------- Some refactorings might take a long time to finish (based on the size of your project). The ``get_changes()`` method of these refactorings take a parameter called ``task_handle``. If you want to monitor or stop these refactoring you can pass a ``rope.refactor.taskhandle.TaskHandle`` to this method. See ``rope.refactor.taskhandle`` module for more information. Basic Implicit Interfaces ------------------------- Implicit interfaces are the interfaces that you don't explicitly define; But you expect a group of classes to have some common attributes. These interfaces are very common in dynamic languages; Since we only have implementation inheritance and not interface inheritance. For instance: .. code-block:: python class A(object): def count(self): pass class B(object): def count(self): pass def count_for(arg): return arg.count() count_for(A()) count_for(B()) Here we know that there is an implicit interface defined by the function ``count_for`` that provides ``count()``. Here when we rename ``A.count()`` we expect ``B.count()`` to be renamed, too. Currently rope supports a basic form of implicit interfaces. When you try to rename an attribute of a parameter, rope renames that attribute for all objects that have been passed to that function in different call sites. That is renaming the occurrence of ``count`` in ``count_for`` function to ``newcount`` will result in: .. code-block:: python class A(object): def newcount(self): pass class B(object): def newcount(self): pass def count_for(arg): return arg.newcount() count_for(A()) count_for(B()) This also works for change method signature. Note that this feature relies on rope's object analysis mechanisms to find out the parameters that are passed to a function. Restructurings -------------- ``rope.refactor.restructure`` can be used for performing restructurings. A restructuring is a program transformation; not as well defined as other refactorings like rename. In this section, we'll see some examples. After this example you might like to have a look at: * ``rope.refactor.restructure`` for more examples and features not described here like adding imports to changed modules. * ``rope.refactor.wildcards`` for an overview of the arguments the default wildcard supports. Finally, restructurings can be improved in many ways (for instance adding new wildcards). You might like to discuss your ideas in the `Github Discussion`_. .. _`Github Discussion`: https://github.com/python-rope/rope/discussions Example 1 ''''''''' In its basic form we have a pattern and a goal. Consider we were not aware of the ``**`` operator and wrote our own: .. code-block:: python def pow(x, y): result = 1 for i in range(y): result *= x return result print(pow(2, 3)) Now that we know ``**`` exists we want to use it wherever ``pow`` is used (there might be hundreds of them!). We can use a pattern like:: pattern: pow(${param1}, ${param2}) Goal can be something like:: goal: ${param1} ** ${param2} Note that ``${...}`` can be used to match expressions. By default every expression at that point will match. You can use the matched names in goal and they will be replaced with the string that was matched in each occurrence. So the outcome of our restructuring will be: .. code-block:: python def pow(x, y): result = 1 for i in range(y): result *= x return result print(2 ** 3) It seems to be working but what if ``pow`` is imported in some module or we have some other function defined in some other module that uses the same name and we don't want to change it. Wildcard arguments come to rescue. Wildcard arguments is a mapping; Its keys are wildcard names that appear in the pattern (the names inside ``${...}``). The values are the parameters that are passed to wildcard matchers. The arguments a wildcard takes is based on its type. For checking the type of a wildcard, we can pass ``type=value`` as an argument; ``value`` should be resolved to a python variable (or reference). For instance for specifying ``pow`` in this example we can use ``mod.pow``. As you see, this string should start from module name. For referencing python builtin types and functions you can use ``__builtin__`` module (for instance ``__builtin__.int``). For solving the mentioned problem, we change our ``pattern``. But ``goal`` remains the same:: pattern: ${pow_func}(${param1}, ${param2}) goal: ${param1} ** ${param2} Consider the name of the module containing our ``pow`` function is ``mod``. ``args`` can be:: pow_func: name=mod.pow If we need to pass more arguments to a wildcard matcher we can use ``,`` to separate them. Such as ``name: type=mod.MyClass,exact``. This restructuring handles aliases like in: .. code-block:: python mypow = pow result = mypow(2, 3) Transforms into: .. code-block:: python mypow = pow result = 2 ** 3 If we want to ignore aliases we can pass ``exact`` as another wildcard argument:: pattern: ${pow}(${param1}, ${param2}) goal: ${param1} ** ${param2} args: pow: name=mod.pow, exact ``${name}``, by default, matches every expression at that point; if ``exact`` argument is passed to a wildcard only the specified name will match (for instance, if ``exact`` is specified , ``${name}`` matches ``name`` and ``x.name`` but not ``var`` nor ``(1 + 2)`` while a normal ``${name}`` can match all of them). For performing this refactoring using rope library see :ref:`library:Restructuring`. Example 2 ''''''''' As another example consider: .. code-block:: python class A(object): def f(self, p1, p2): print(p1) print(p2) a = A() a.f(1, 2) Later we decide that ``A.f()`` is doing too much and we want to divide it to ``A.f1()`` and ``A.f2()``: .. code-block:: python class A(object): def f(self, p1, p2): print(p1) print(p2) def f1(self, p): print(p) def f2(self, p): print(p) a = A() a.f(1, 2) But who's going to fix all those nasty occurrences (actually this situation can be handled using inline method refactoring but this is just an example; consider inline refactoring is not implemented yet!). Restructurings come to rescue:: pattern: ${inst}.f(${p1}, ${p2}) goal: ${inst}.f1(${p1}) ${inst}.f2(${p2}) args: inst: type=mod.A After performing we will have: .. code-block:: python class A(object): def f(self, p1, p2): print(p1) print(p2) def f1(self, p): print(p) def f2(self, p): print(p) a = A() a.f1(1) a.f2(2) Example 3 ''''''''' If you like to replace every occurrences of ``x.set(y)`` with ``x = y`` when x is an instance of ``mod.A`` in: .. code-block:: python from mod import A a = A() b = A() a.set(b) We can perform a restructuring with these information:: pattern: ${x}.set(${y}) goal: ${x} = ${y} args: x: type=mod.A After performing the above restructuring we'll have: .. code-block:: python from mod import A a = A() b = A() a = b Note that ``mod.py`` contains something like: .. code-block:: python class A(object): def set(self, arg): pass Issues '''''' Pattern names can appear only at the start of an expression. For instance ``var.${name}`` is invalid. These situations can usually be fixed by specifying good checks, for example on the type of `var` and using a ``${var}.name``. Object Inference ================ This section is a bit out of date. Static object inference can do more than described here (see unittests). Hope to update this someday! Static Object Inference ----------------------- .. code-block:: python class AClass(object): def __init__(self): self.an_attr = 1 def call_a_func(self): return a_func() def a_func(): return AClass() a_var = a_func() #a_var.${codeassist} another_var = a_var #another_var.${codeassist} #another_var.call_a_func().${codeassist} Basic support for builtin types: .. code-block:: python a_list = [AClass(), AClass()] for x in a_list: pass #x.${codeassist} #a_list.pop().${codeassist} a_dict = ['text': AClass()] for key, value in a_dict.items(): pass #key.${codeassist} #value.${codeassist} Enhanced static returned object inference: .. code-block:: python class C(object): def c_func(self): return [''] def a_func(arg): return arg.c_func() a_var = a_func(C()) Here rope knows that the type of a_var is a ``list`` that holds ``str``\s. Supporting generator functions: .. code-block:: python class C(object): pass def a_generator(): yield C() for c in a_generator(): a_var = c Here the objects ``a_var`` and ``c`` hold are known. Rope collects different types of data during SOA, like per name data for builtin container types: .. code-block:: python l1 = [C()] var1 = l1.pop() l2 = [] l2.append(C()) var2 = l2.pop() Here rope can easily infer the type of ``var1``. But for knowing the type of ``var2``, it needs to analyze the items inserted into ``l2`` which might happen in other modules. Rope can do that by running SOA on that module. You might be wondering is there any reason for using DOA instead of SOA. The answer is that DOA might be more accurate and handles complex and dynamic situations. For example in: .. code-block:: python def f(arg): return eval(arg) a_var = f('C') SOA can no way conclude the object ``a_var`` holds but it is really trivial for DOA. What's more SOA only analyzes calls in one module while DOA analyzes any call that happens when running a module. That is, for achieving the same result as DOA you might need to run SOA on more than one module and more than once (not considering dynamic situations.) One advantage of SOA is that it is much faster than DOA. Dynamic Object Analysis ----------------------- ``PyCore.run_module()`` runs a module and collects object information if ``perform_doa`` project config is set. Since as the program runs rope gathers type information, the program runs much slower. After the program is run, you can get better code assists and some of the refactorings perform much better. ``mod1.py``: .. code-block:: python def f1(param): pass #param.${codeassist} #f2(param).${codeassist} def f2(param): #param.${codeassist} return param Using code assist in specified places does not give any information and there is actually no information about the return type of ``f2`` or ``param`` parameter of ``f1``. ``mod2.py``: .. code-block:: python import mod1 class A(object): def a_method(self): pass a_var = A() mod1.f1(a_var) Retry those code assists after performing DOA on ``mod2`` module. Builtin Container Types ''''''''''''''''''''''' Builtin types can be handled in a limited way, too: .. code-block:: python class A(object): def a_method(self): pass def f1(): result = [] result.append(A()) return result returned = f() #returned[0].${codeassist} Test the the proposed completions after running this module. Guessing Function Returned Value Based On Parameters ---------------------------------------------------- ``mod1.py``: .. code-block:: python class C1(object): def c1_func(self): pass class C2(object): def c2_func(self): pass def func(arg): if isinstance(arg, C1): return C2() else: return C1() func(C1()) func(C2()) After running ``mod1`` either SOA or DOA on this module you can test: ``mod2.py``: .. code-block:: python import mod1 arg = mod1.C1() a_var = mod1.func(arg) a_var.${codeassist} mod1.func(mod1.C2()).${codeassist} Automatic SOA ------------- When turned on, it analyzes the changed scopes of a file when saving for obtaining object information; So this might make saving files a bit more time consuming. By default, this feature is turned on, but you can turn it off by editing your project ``config.py`` file, though that is not recommended. Validating Object DB -------------------- Since files on disk change over time project objectdb might hold invalid information. Currently there is a basic incremental objectdb validation that can be used to remove or fix out of date information. Rope uses this feature by default but you can disable it by editing ``config.py``. Type Hinting ------------ Currently supported type hinting for: - function parameter type, using function doctring (:type or @type) - function return type, using function doctring (:rtype or @rtype) - class attribute type, using class docstring (:type or @type). Attribute should by set to None or NotImplemented in class. - any assignment, using type comments of PEP 0484 (in limited form). If rope cannot detect the type of a function argument correctly (due to the dynamic nature of Python), you can help it by hinting the type using one of the following docstring syntax styles. **Sphinx style** http://sphinx-doc.org/domains.html#info-field-lists :: def myfunction(node, foo): """Do something with a ``node``. :type node: ProgramNode :param str foo: foo parameter description """ node.| # complete here **Epydoc** http://epydoc.sourceforge.net/manual-fields.html :: def myfunction(node): """Do something with a ``node``. @type node: ProgramNode """ node.| # complete here **Numpydoc** https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt In order to support the numpydoc format, you need to install the `numpydoc `__ package. :: def foo(var1, var2, long_var_name='hi'): r"""A one-line summary that does not use variable names or the function name. ... Parameters ---------- var1 : array_like Array_like means all those objects -- lists, nested lists, etc. -- that can be converted to an array. We can also refer to variables like `var1`. var2 : int The type above can either refer to an actual Python type (e.g. ``int``), or describe the type of the variable in more detail, e.g. ``(N,) ndarray`` or ``array_like``. long_variable_name : {'hi', 'ho'}, optional Choices in brackets, default first when optional. ... """ var2.| # complete here **PEP 0484** https://www.python.org/dev/peps/pep-0484/#type-comments :: class Sample(object): def __init__(self): self.x = None # type: random.Random self.x.| # complete here Supported syntax of type hinting '''''''''''''''''''''''''''''''' Currently rope supports the following syntax of type-hinting. Parametrized objects: - Foo - foo.bar.Baz - list[Foo] or list[foo.bar.Baz] etc. - set[Foo] - tuple[Foo] - dict[Foo, Bar] - collections.Iterable[Foo] - collections.Iterator[Foo] Nested expressions also allowed: - collections.Iterable[list[Foo]] TODO: Callable objects: - (Foo, Bar) -> Baz Multiple interfaces implementation: - Foo | Bar Custom Source Folders ===================== By default rope searches the project for finding source folders (folders that should be searched for finding modules). You can add paths to that list using ``source_folders`` project config. Note that rope guesses project source folders correctly most of the time. You can also extend python path using ``python_path`` config. Version Control Systems Support =============================== When performing refactorings some files might need to be moved (when renaming a module) or new files might be created. When using a VCS, rope detects and uses it to perform file system actions. Currently Mercurial_, GIT_, Darcs_ and SVN (using pysvn_ library) are supported. They are selected based on dot files in project root directory. For instance, Mercurial will be used if `mercurial` module is available and there is a ``.hg`` folder in project root. Rope assumes either all files are under version control in a project or there is no version control at all. Also don't forget to commit your changes yourself, rope doesn't do that. Adding support for other VCSs is easy; have a look at :ref:`library:Writing A \`FileSystemCommands\``. .. _pysvn: http://pysvn.tigris.org .. _Mercurial: http://selenic.com/mercurial .. _GIT: http://git.or.cz .. _darcs: http://darcs.net --- # Source: https://github.com/python-rope/rope/blob/master/docs/release-process.rst Release Process =============== Pre-Release ----------- 1. Update :ref:`gha-cache-key.txt `: ``pip-compile --extra dev --generate-hashes -o gha-cache-key.txt --resolver=backtracking`` Release ------- 1. Ensure tickets assigned to Milestones are up to date 2. Update ``CHANGELOG.md`` 3. Close milestone 4. Increment version number in ``pyproject.toml`` 5. `git commit && git push` 6. Tag the release with the tag annotation containing the release information, ``python bin/tag-release.py`` 7. Create Github Release 8. Publish release announcements to GitHub Discussions Release Schedule ================ Rope has a release schedule once a month, usually sometime close to the 15th of each month. However, this schedule is not a guaranteed date, if there is a particularly urgent change or if there's not enough pull requests for the month, there may be additional releases or the release window may be skipped. --- # Source: https://github.com/python-rope/rope/blob/master/docs/rope.rst Features ======== Features implemented so far: * Refactorings * Rename everything! * Extract method/local variable * Move class/function/module/package/method * Inline method/local variable/parameter * Restructuring (like converting ``${a}.f(${b})`` to ``${b}.g(${a})`` where ``a: type=mymod.A``) * Introduce factory * Change method signature * Transform module to package * Encapsulate field * Replace method with method object * And a few others * Refactoring Features * Extracting similar statements in extract refactorings * Fixing imports when needed * Previewing refactorings * Undo/redo refactorings * Stopping refactorings * Cross-project refactorings * Basic implicit interfaces handling in rename and change signature * Mercurial_, GIT_, Darcs_ and SVN (pysvn_ library) support in refactorings * IDE helpers * Auto-completion * Definition location * Get pydoc * Find occurrences * Organize imports (remove unused and duplicate imports and sort them) * Generating python elements * Object Inference * Static and dynamic object analysis * Handling built-in container types * Saving object information on disk and validating them * Type hints using docstring or type comments PEP 0484 For more information see :ref:`overview:Rope Overview`. .. _pysvn: http://pysvn.tigris.org .. _Mercurial: http://selenic.com/mercurial .. _GIT: http://git.or.cz .. _darcs: http://darcs.net