Extended refactoring capabilities for Python LSP Server using Rope.

Overview

pylsp-rope

Tests

Extended refactoring capabilities for Python LSP Server using Rope.

This is a plugin for Python LSP Server, so you also need to have it installed.

python-lsp-server already has basic built-in support for using Rope, but it's currently limited to just renaming and completion. Installing this plugin adds more refactoring functionality to python-lsp-server.

Installation

To use this plugin, you need to install this plugin in the same virtualenv as python-lsp-server itself.

pip install pylsp-rope

Then run pylsp as usual, the plugin will be auto-discovered by python-lsp-server if you've installed it to the right environment. Refer to your IDE/text editor's documentation on how to setup a language server in your IDE/text editor.

Configuration

There is no configuration yet.

Features

This plugin adds the following features to python-lsp-server:

  • extract method (codeAction)
  • extract variable (codeAction)
  • inline method/variable/parameter (codeAction)
  • use function (codeAction)
  • method to method object (codeAction)
  • more to come...

Refer to Rope documentation for more details on how these refactoring works.

Usage

Extract method

This refactoring works by triggering a CodeAction when selecting a block of code.

Extract variable

This refactoring works by triggering a CodeAction when selecting a Python expression.

Inline

This refactoring works by triggering a CodeAction when the cursor is on a resolvable Python identifier.

Use function

This works by triggering a CodeAction when the cursor is on the function name of a def statement.

Method to method object

This works by triggering a CodeAction when the cursor is on the function name of a def statement.

Caveat

Support for working on unsaved document is currently incomplete.

Before you start refactoring you must save all unsaved changes in your text editor. I highly recommended that you enable autosave on your text editor.

This plugin is in early development, so expect some bugs. Please report in Github issue tracker if you had any issues with the plugin.

Developing

See CONTRIBUTING.md.

Credits

This package was created with Cookiecutter from lieryan/cookiecutter-pylsp-plugin project template.

Comments
  • code actions fail if `pyproject.toml` has no `tool` section

    code actions fail if `pyproject.toml` has no `tool` section

    • pylsp-rope version: 0.1.9
    • Text editor/IDE/LSP Client: Neovim 0.7.2
    • Python version: 3.9.13
    • Operating System: Ubuntu 22.04 WSL on Windows 10

    Description

    I wanted to use code actions by calling :lua vim.lsp.buf.code_action() but get a message that there are no code actions available. When the pyproject.toml file exists and has sections, but no [tool] section it seems pylsp-rope runs in to an exception. If any other sections exist there is a KeyError('tool'). If the file is empty there is an AssertionError. Both seem to originate in pytoolconfig.

    It works fine when I either remove this pyproject.toml file or just add an empty [tool] section like this:

    [tool]
    

    Details

    Output of :LspLog in neovim

    [ERROR][2022-07-29 15:34:51] .../vim/lsp/rpc.lua:420    "rpc"   "pylsp" "stderr"        "2022-07-29 15:34:51,827 CEST - WARNING
         - pylsp.config.config - Failed to load hook pylsp_code_actions: 'tool'\n"
    

    Reproduction

    Here is a parametrized test that will reproduce the error:

    @pytest.mark.parametrize("content", ["", "[example]\n", "[tool]\n"])
    def test_pyproject_toml_no_tool_section(
        tmpdir, config, document, workspace, code_action_context, content
    ):
        pathlib.Path(workspace.root_path, "pyproject.toml").write_text(content)
    
        response = plugin.pylsp_code_actions(
            config=config,
            workspace=workspace,
            document=document,
            range=Range((0, 0), (0, 0)),
            context=code_action_context,
        )
    
    opened by MrGreenTea 6
  • Pip build includes `test` package

    Pip build includes `test` package

    The pip package bundles the test package, which pollutes the global namespace. In particular, the AUR package python-pylsp-rope occupies /usr/lib/python3.10/site-packages/test.

    opened by lukasjuhrich 5
  • Tests failing (help debugging)

    Tests failing (help debugging)

    • pylsp-rope version: latest release
    • Text editor/IDE/LSP Client: none
    • Python version: 3.9.9
    • Operating System: void linux

    Description

    I'm trying to package pylsp-rope for guix but tests are failing. Can someone help me debug the reason or give me suggestions of what to try next. I ran pytest -vv in the project root.

    Here's the guix package for pylsp-rope, with tests disabled, for the curious:

    (define-public python-pylsp-rope
      (package
        (name "python-pylsp-rope")
        (version "0.1.9")
        (source
          (origin
            (method url-fetch)
            (uri (pypi-uri "pylsp-rope" version))
            (sha256
              (base32 "0r26icb5iaf5ry46xms3wmy8prw0lxgl84spgkby4q1dxap5bbk7"))))
        (build-system python-build-system)
        (arguments
          '(#:tests? #f
            #:phases
            (modify-phases %standard-phases
              (replace 'check
                (lambda* (#:key tests? inputs outputs #:allow-other-keys)
                  (when tests?
                    (invoke "pytest" "-vv")))))))
        (propagated-inputs
          (list python-lsp-server python-rope python-typing-extensions))
        (native-inputs (list python-pytest python-twine))
        (home-page "https://github.com/python-rope/pylsp-rope")
        (synopsis
          "Extended refactoring capabilities for Python LSP Server using Rope.")
        (description
          "Extended refactoring capabilities for Python LSP Server using Rope.")
        (license license:expat)))
    
    
    

    Details

    Here's a full paste of the test failure on SourceHut as GitHub did not allow me to post a code block this large:

    https://paste.sr.ht/~whereiseveryone/b0bf2b2ee97d140268b887177b9826390d3d17df

    ============================= test session starts ==============================
    platform linux -- Python 3.9.9, pytest-6.2.5, py-1.10.0, pluggy-0.13.1 -- /gnu/store/j3cx0yaqdpw0mxizp5bayx93pya44dhn-python-wrapper-3.9.9/bin/python
    cachedir: .pytest_cache
    hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pylsp-rope-0.1.9/.hypothesis/examples')
    rootdir: /tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pylsp-rope-0.1.9
    plugins: hypothesis-6.0.2
    collecting ... collected 27 items
    
    test/test_commands.py::test_command_registration ERROR                   [  3%]
    test/test_commands.py::test_command_error_handling ERROR                 [  7%]
    test/test_commands.py::test_command_nothing_to_modify ERROR              [ 11%]
    test/test_extract.py::test_extract_variable ERROR                        [ 14%]
    test/test_extract.py::test_extract_variable_with_similar ERROR           [ 18%]
    test/test_extract.py::test_extract_global_variable ERROR                 [ 22%]
    test/test_extract.py::test_extract_global_variable_with_similar ERROR    [ 25%]
    test/test_extract.py::test_extract_variable_not_offered_when_selecting_non_expression ERROR [ 29%]
    test/test_extract.py::test_extract_method ERROR                          [ 33%]
    test/test_extract.py::test_extract_method_with_similar ERROR             [ 37%]
    test/test_extract.py::test_extract_global_method ERROR                   [ 40%]
    test/test_extract.py::test_extract_method_global_with_similar ERROR      [ 44%]
    test/test_import_utils.py::test_organize_import ERROR                    [ 48%]
    test/test_inline.py::test_inline ERROR                                   [ 51%]
    test/test_inline.py::test_inline_not_offered_when_selecting_unsuitable_range ERROR [ 55%]
    test/test_introduce_parameter.py::test_introduce_parameter ERROR         [ 59%]
    test/test_local_to_field.py::test_local_to_field ERROR                   [ 62%]
    test/test_local_to_field.py::test_local_to_field_not_offered_when_selecting_unsuitable_range ERROR [ 66%]
    test/test_lsp_diff.py::test_lsp_diff ERROR                               [ 70%]
    test/test_lsp_diff.py::test_difflib_ops_to_text_edit_ops_insert ERROR    [ 74%]
    test/test_lsp_diff.py::test_difflib_ops_to_text_edit_ops_delete ERROR    [ 77%]
    test/test_lsp_diff.py::test_difflib_ops_to_text_edit_ops_replace ERROR   [ 81%]
    test/test_method_to_method_object.py::test_method_to_method_object ERROR [ 85%]
    test/test_method_to_method_object.py::test_method_to_method_object_not_offered_when_selecting_unsuitable_range ERROR [ 88%]
    test/test_project.py::test_rope_changeset_to_workspace_changeset ERROR   [ 92%]
    test/test_usefunction.py::test_use_function_globally ERROR               [ 96%]
    test/test_usefunction.py::test_use_function_in_current_file ERROR        [100%]
    
    ==================================== ERRORS ====================================
    _________________ ERROR at setup of test_command_registration ______________
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
       
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    __________ ERROR at setup of test_difflib_ops_to_text_edit_ops_delete __________
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_difflib_ops_to_text_edit_1')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    _________ ERROR at setup of test_difflib_ops_to_text_edit_ops_replace __________
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_difflib_ops_to_text_edit_2')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    ________________ ERROR at setup of test_method_to_method_object ________________
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_method_to_method_object0')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    _ ERROR at setup of test_method_to_method_object_not_offered_when_selecting_unsuitable_range _
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_method_to_method_object_n0')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    _________ ERROR at setup of test_rope_changeset_to_workspace_changeset _________
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_rope_changeset_to_workspa0')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    _________________ ERROR at setup of test_use_function_globally _________________
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_use_function_globally0')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    _____________ ERROR at setup of test_use_function_in_current_file ______________
    
    tmpdir = local('/tmp/guix-build-python-pylsp-rope-0.1.9.drv-0/pytest-of-nixbld/pytest-0/test_use_function_in_current_f0')
    
        @pytest.fixture
        def workspace(tmpdir):
            """Return a workspace."""
            ws = Workspace(uris.from_fs_path(str(tmpdir)), Mock())
    >       ws._config = Config(ws.root_uri, {}, 0, {})
    
    test/conftest.py:31: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /gnu/store/6kpbc372zy1313x5shd46crp02vj129q-python-lsp-server-1.3.3/lib/python3.9/site-packages/pylsp/config/config.py:52: in __init__
        entry_point.load()
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2449: in load
        self.require(*args, **kwargs)
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:2472: in require
        items = working_set.resolve(reqs, env, installer, extras=self.extras)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <pkg_resources.WorkingSet object at 0x7ffff5f09880>
    requirements = [Requirement.parse('python-lsp-server')], env = None
    installer = None, replace_conflicting = False, extras = ()
    
        def resolve(self, requirements, env=None, installer=None,  # noqa: C901
                    replace_conflicting=False, extras=None):
            """List all distributions needed to (recursively) meet `requirements`
        
            `requirements` must be a sequence of ``Requirement`` objects.  `env`,
            if supplied, should be an ``Environment`` instance.  If
            not supplied, it defaults to all distributions available within any
            entry or distribution in the working set.  `installer`, if supplied,
            will be invoked with each requirement that cannot be met by an
            already-installed distribution; it should return a ``Distribution`` or
            ``None``.
        
            Unless `replace_conflicting=True`, raises a VersionConflict exception
            if
            any requirements are found on the path that have the correct name but
            the wrong version.  Otherwise, if an `installer` is supplied it will be
            invoked to obtain the correct version of the requirement and activate
            it.
        
            `extras` is a list of the extras to be used with these requirements.
            This is important because extra requirements may look like `my_req;
            extra = "my_extra"`, which would otherwise be interpreted as a purely
            optional requirement.  Instead, we want to be able to assert that these
            requirements are truly required.
            """
        
            # set up the stack
            requirements = list(requirements)[::-1]
            # set of processed requirements
            processed = {}
            # key -> dist
            best = {}
            to_activate = []
        
            req_extras = _ReqExtras()
        
            # Mapping of requirement to set of distributions that required it;
            # useful for reporting info about conflicts.
            required_by = collections.defaultdict(set)
        
            while requirements:
                # process dependencies breadth-first
                req = requirements.pop(0)
                if req in processed:
                    # Ignore cyclic or redundant dependencies
                    continue
        
                if not req_extras.markers_pass(req, extras):
                    continue
        
                dist = best.get(req.key)
                if dist is None:
                    # Find the best distribution and add it to the map
                    dist = self.by_key.get(req.key)
                    if dist is None or (dist not in req and replace_conflicting):
                        ws = self
                        if env is None:
                            if dist is None:
                                env = Environment(self.entries)
                            else:
                                # Use an empty environment and workingset to avoid
                                # any further conflicts with the conflicting
                                # distribution
                                env = Environment([])
                                ws = WorkingSet([])
                        dist = best[req.key] = env.best_match(
                            req, ws, installer,
                            replace_conflicting=replace_conflicting
                        )
                        if dist is None:
                            requirers = required_by.get(req, None)
                            raise DistributionNotFound(req, requirers)
                    to_activate.append(dist)
                if dist not in req:
                    # Oops, the "best" so far conflicts with a dependency
                    dependent_req = required_by[req]
    >               raise VersionConflict(dist, req).with_context(dependent_req)
    E               pkg_resources.VersionConflict: (rope 0.19.0 (/gnu/store/z74kc0fyy71vmw7wx3pln8yl0fw3bsc6-python-rope-0.19.0/lib/python3.9/site-packages), Requirement.parse('rope>=0.21.0'))
    
    /gnu/store/b6j1qw1a5rkbfvcy7lc9fm95abbzpa4x-python-3.9.9/lib/python3.9/site-packages/pkg_resources/__init__.py:777: VersionConflict
    =========================== short test summary info ============================
    ERROR test/test_commands.py::test_command_registration - pkg_resources.Versio...
    ERROR test/test_commands.py::test_command_error_handling - pkg_resources.Vers...
    ERROR test/test_commands.py::test_command_nothing_to_modify - pkg_resources.V...
    ERROR test/test_extract.py::test_extract_variable - pkg_resources.VersionConf...
    ERROR test/test_extract.py::test_extract_variable_with_similar - pkg_resource...
    ERROR test/test_extract.py::test_extract_global_variable - pkg_resources.Vers...
    ERROR test/test_extract.py::test_extract_global_variable_with_similar - pkg_r...
    ERROR test/test_extract.py::test_extract_variable_not_offered_when_selecting_non_expression
    ERROR test/test_extract.py::test_extract_method - pkg_resources.VersionConfli...
    ERROR test/test_extract.py::test_extract_method_with_similar - pkg_resources....
    ERROR test/test_extract.py::test_extract_global_method - pkg_resources.Versio...
    ERROR test/test_extract.py::test_extract_method_global_with_similar - pkg_res...
    ERROR test/test_import_utils.py::test_organize_import - pkg_resources.Version...
    ERROR test/test_inline.py::test_inline - pkg_resources.VersionConflict: (rope...
    ERROR test/test_inline.py::test_inline_not_offered_when_selecting_unsuitable_range
    ERROR test/test_introduce_parameter.py::test_introduce_parameter - pkg_resour...
    ERROR test/test_local_to_field.py::test_local_to_field - pkg_resources.Versio...
    ERROR test/test_local_to_field.py::test_local_to_field_not_offered_when_selecting_unsuitable_range
    ERROR test/test_lsp_diff.py::test_lsp_diff - pkg_resources.VersionConflict: (...
    ERROR test/test_lsp_diff.py::test_difflib_ops_to_text_edit_ops_insert - pkg_r...
    ERROR test/test_lsp_diff.py::test_difflib_ops_to_text_edit_ops_delete - pkg_r...
    ERROR test/test_lsp_diff.py::test_difflib_ops_to_text_edit_ops_replace - pkg_...
    ERROR test/test_method_to_method_object.py::test_method_to_method_object - pk...
    ERROR test/test_method_to_method_object.py::test_method_to_method_object_not_offered_when_selecting_unsuitable_range
    ERROR test/test_project.py::test_rope_changeset_to_workspace_changeset - pkg_...
    ERROR test/test_usefunction.py::test_use_function_globally - pkg_resources.Ve...
    ERROR test/test_usefunction.py::test_use_function_in_current_file - pkg_resou...
    ============================== 27 errors in 3.54s ==============================
    error: in phase 'check': uncaught exception:
    %exception #<&invoke-error program: "pytest" arguments: ("-vv") exit-status: 1 term-signal: #f stop-signal: #f> 
    phase `check' failed after 4.8 seconds
    command "pytest" "-vv" failed with status 1
    builder for `/gnu/store/dsjpj80dam22kjvi6vnx626m4a6ri9k1-python-pylsp-rope-0.1.9.drv' failed with exit code 1
    build of /gnu/store/dsjpj80dam22kjvi6vnx626m4a6ri9k1-python-pylsp-rope-0.1.9.drv failed
    View build log at '/var/log/guix/drvs/ds/jpj80dam22kjvi6vnx626m4a6ri9k1-python-pylsp-rope-0.1.9.drv.bz2'.
    guix build: error: build of `/gnu/store/dsjpj80dam22kjvi6vnx626m4a6ri9k1-python-pylsp-rope-0.1.9.drv' failed
    
    
    
    opened by jgarte 2
  • [Question] Need to add

    [Question] Need to add "typing_extensions" to dependencies?

    • pylsp-rope version: 0.1.5
    • Text editor/IDE/LSP Client: coc.nvim
    • Python version: 3.10
    • Operating System: macOS

    Description

    Hi, typing_extensions is required for "pylsp-rope" to work, right? (Checking the code of "pylsp-rope", it seems that typing_extensions is used)

    Normally, typing_extensions is not included when python-lsp-server is installed.

    DEMO (mp4)

    • 1st: pip install python-lsp-server[all] pylsp-rope
      • [NG] Not working
    • Add: pip install typing_extensions
      • [OK] It's working

    https://user-images.githubusercontent.com/188642/136782751-77aebb1c-11a1-451e-b1ee-d5663560e6cf.mp4

    Note

    If you install pylsp-mypy, typing_extensions will be installed since mypy is present

    If you add typing_extensions to your pylsp-rope dependencies, you may need to be careful not to conflict with the pylsp-mypy version.

    Misc

    opened by yaegassy 1
  • Don't make the package >= 3.9 only

    Don't make the package >= 3.9 only

    • pylsp-rope version: 0.1.2
    • Text editor/IDE/LSP Client: any
    • Python version: 3.6, 3.8
    • Operating System: Linux (packaging pylsp-rope for openSUSE/Factory)

    Description

    pylsp_rope/project.py uses decorator functools.cache which was however introduced only in Python 3.9. It is pity to limit this plugin just to Python 3.9, when python-lsp is still 3.6+.

    This patch makes the code testable on all supported Python 3 versions.

    opened by mcepl 1
  • CoC would not call the `workspace/executeCommand` in response to codeAction

    CoC would not call the `workspace/executeCommand` in response to codeAction

    pylsp-rope version: 0.1.2 Text editor/IDE/LSP Client: Vim, Neovim with coc.nvim Python version: Python 3.9.5

    Description

    Adding details from this discussion thread.

    Doing codeAction with CoC.nvim doesn't seem to work at all.

    I was using this mapping to trigger codeAction in CoC:

    map gca <Plug>(coc-codeaction-selected)
    

    and pylsp-rope responded with a codeAction response that looks like this:

    [
      {
        "title": "Extract method",
        "kind": "refactor.extract",
        "command": {
          "command": "pylsp_rope.refactor.extract.method",
          "arguments": {
            "document_uri": "file:///tmp/pytest-of-lieryan/pytest-530/test_extract_variable0/simple.py",
            "range": {
              "start": { "line": 4, "character": 10 },
              "end": { "line": 4, "character": 26 }
            }
          }
        }
      }
    ]
    

    And the code action selector showed up fine:

    Screenshot from 2021-10-04 13-58-53

    but when I pressed Enter to select the command to execute, CoC would not call the workspace/executeCommand and instead printed this rather non-descript error:

    [coc.nvim]: Error on "codeAction": Found non-callable @@iterator                                                                                                                                                                                                    
    

    I would have expected CoC to make an "pylsp_rope.refactor.extract.method" request to the server instead.

    I can definitely confirm that the LSP server never got to receive the workspace/executeCommand request, as python-lsp-server logging shows that the pylsp_execute_command() was never called at all.

    For context, vim-lsp and ALE both worked perfectly fine so it doesn't seem to be that pylsp-rope are doing something that would be completely bogus.

    opened by lieryan 0
  • Feature: rename

    Feature: rename

    • [x] #15
    • [ ] rename should be toggleable with a config
    • [ ] create PR to remove rename from pylsp core
    • [ ] pylsp core should have optional dependency on pylsp-rope to add support for rope-based rename
    opened by lieryan 0
  • Feature: autocomplete

    Feature: autocomplete

    • [ ] Implement autocomplete in pylsp-rope
    • [ ] autocomplete should be toggleable with a config
    • [ ] create PR to remove autocomplete from pylsp core
    • [ ] pylsp core should have optional dependency on pylsp-rope to add support for rope-based autocomplete
    opened by lieryan 0
  • Refactorings when cursor on name

    Refactorings when cursor on name

    • pylsp-rope version: 0.1.6
    • Text editor/IDE/LSP Client: nvim with coc-pylsp
    • Python version: 3.9.7
    • Operating System: Linux Manjaro

    Description

    When run codeaction-selected with cursor on method invocation, it shows only extract method refactoring. When visual select function name, it shows many refactorings.

    I don't know if the problem is with rope-pylsp, python-lsp-server or coc.

    coc-pyright shows many refactoring on cursor.

    Details

    Logs for invocation with cursor on method name

    2021-10-19 08:19:41,331 CEST - DEBUG - pylsp_jsonrpc.endpoint - Handling request from client {'jsonrpc': '2.0', 'id': 6, 'method': 'textDocument/codeAction', 'params': {'textDocument': {'uri': 'file:///....py'}, 'range': {'start': {'line': 24, 'character': 0}, 'end': {'line': 25, 'character': 0}}, 'context': {'diagnostics': []}}}
    2021-10-19 08:19:41,332 CEST - DEBUG - pylsp.config.config -   pylsp_code_actions [hook]
          config: <pylsp.config.config.Config object at 0x7f3574bdb850>
          workspace: <pylsp.workspace.Workspace object at 0x7f3574b740d0>
          document: file:///...py
          range: {'start': {'line': 24, 'character': 0}, 'end': {'line': 25, 'character': 0}}
          context: {'diagnostics': []}
    
    2021-10-19 08:19:41,333 CEST - INFO - pylsp_rope.plugin - textDocument/codeAction: file:///....py {'start': {'line': 24, 'character': 0}, 'end': {'line': 25, 'character': 0}} {'diagnostics': []}
    2021-10-19 08:19:41,336 CEST - DEBUG - pylsp.config.config -   finish pylsp_code_actions --> [[{'title': 'Extract method', 'kind': 'refactor.extract', 'command': {'title': 'Extract method', 'command': 'pylsp_rope.refactor.extract.method', 'arguments': [{'document_uri': 'file:///....py', 'range': {'start': {'line': 24, 'character': 0}, 'end': {'line': 25, 'character': 0}}}]}}]] [hook]
    
    2021-10-19 08:19:41,336 CEST - DEBUG - pylsp_jsonrpc.endpoint - Got result from synchronous request handler: [{'title': 'Extract method', 'kind': 'refactor.extract', 'command': {'title': 'Extract method', 'command': 'pylsp_rope.refactor.extract.method', 'arguments': [{'document_uri': 'file:///....py', 'range': {'start': {'line': 24, 'character': 0}, 'end': {'line': 25, 'character': 0}}}]}}]
    
    2021-10-19 08:19:57,081 CEST - DEBUG - pylsp_jsonrpc.endpoint - Handling request from client {'jsonrpc': '2.0', 'id': 7, 'method': 'textDocument/codeAction', 'params': {'textDocument': {'uri': 'file://....py'}, 'range': {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}}, 'context': {'diagnostics': []}}}
    2021-10-19 08:19:57,081 CEST - DEBUG - pylsp.config.config -   pylsp_code_actions [hook]
          config: <pylsp.config.config.Config object at 0x7f3574bdb850>
          workspace: <pylsp.workspace.Workspace object at 0x7f3574b740d0>
          document: file:///....py
          range: {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}}
          context: {'diagnostics': []}
    
    2021-10-19 08:19:57,081 CEST - INFO - pylsp_rope.plugin - textDocument/codeAction: file:///....py {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}} {'diagnostics': []}
    2021-10-19 08:19:57,118 CEST - DEBUG - pylsp.config.config -   finish pylsp_code_actions --> [[{'title': 'Extract method', 'kind': 'refactor.extract', 'command': {'title': 'Extract method', 'command': 'pylsp_rope.refactor.extract.method', 'arguments': [{'document_uri': 'file:///....py', 'range': {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}}}]}}, {'title': 'Extract variable', 'kind': 'refactor.extract', 'command': {'title': 'Extract variable', 'command': 'pylsp_rope.refactor.extract.variable', 'arguments': [{'document_uri': 'file:///...py', 'range': {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}}}]}}, {'title': 'Inline method/variable/parameter', 'kind': 'refactor.inline', 'command': {'title': 'Inline method/variable/parameter', 'command': 'pylsp_rope.refactor.inline', 'arguments': [{'document_uri': 'file:///....py', 'position': {'line': 24, 'character': 13}}]}}, {'title': 'Use function', 'kind': 'refactor', 'command': {'title': 'Use function', 'command': 'pylsp_rope.refactor.use_function', 'arguments': [{'document_uri': 'file:///...py', 'position': {'line': 24, 'character': 13}}]}}, {'title': 'Use function for current file only', 'kind': 'refactor', 'command': {'title': 'Use function for current file only', 'command': 'pylsp_rope.refactor.use_function', 'arguments': [{'document_uri': 'file:///...py', 'position': {'line': 24, 'character': 13}, 'documents': ['file:///....py']}]}}, {'title': 'To method object', 'kind': 'refactor.rewrite', 'command': {'title': 'To method object', 'command': 'pylsp_rope.refactor.method_to_method_object', 'arguments': [{'document_uri': 'file:///....py', 'position': {'line': 24, 'character': 13}}]}}]] [hook]
    
    2021-10-19 08:19:57,118 CEST - DEBUG - pylsp_jsonrpc.endpoint - Got result from synchronous request handler: [{'title': 'Extract method', 'kind': 'refactor.extract', 'command': {'title': 'Extract method', 'command': 'pylsp_rope.refactor.extract.method', 'arguments': [{'document_uri': 'file:///....py', 'range': {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}}}]}}, {'title': 'Extract variable', 'kind': 'refactor.extract', 'command': {'title': 'Extract variable', 'command': 'pylsp_rope.refactor.extract.variable', 'arguments': [{'document_uri': 'file:///...py', 'range': {'start': {'line': 24, 'character': 13}, 'end': {'line': 24, 'character': 23}}}]}}, {'title': 'Inline method/variable/parameter', 'kind': 'refactor.inline', 'command': {'title': 'Inline method/variable/parameter', 'command': 'pylsp_rope.refactor.inline', 'arguments': [{'document_uri': 'file:///h...py', 'position': {'line': 24, 'character': 13}}]}}, {'title': 'Use function', 'kind': 'refactor', 'command': {'title': 'Use function', 'command': 'pylsp_rope.refactor.use_function', 'arguments': [{'document_uri': 'file:///...py', 'position': {'line': 24, 'character': 13}}]}}, {'title': 'Use function for current file only', 'kind': 'refactor', 'command': {'title': 'Use function for current file only', 'command': 'pylsp_rope.refactor.use_function', 'arguments': [{'document_uri': 'file:///...py', 'position': {'line': 24, 'character': 13}, 'documents': ['file:///....py']}]}}, {'title': 'To method object', 'kind': 'refactor.rewrite', 'command': {'title': 'To method object', 'command': 'pylsp_rope.refactor.method_to_method_object', 'arguments': [{'document_uri': 'file:///...py', 'position': {'line': 24, 'character': 13}}]}}]
    
    opened by climbus 2
Releases(0.1.8)
  • 0.1.8(Dec 17, 2021)

    New features

    • Add refactor extract method/variable including similar statements variant
    • Add refactor extract global method/variable variant
    Source code(tar.gz)
    Source code(zip)
boofuzz: Network Protocol Fuzzing for Humans

boofuzz: Network Protocol Fuzzing for Humans Boofuzz is a fork of and the successor to the venerable Sulley fuzzing framework. Besides numerous bug fi

Joshua Pereyda 1.7k Dec 31, 2022
pfSense integration with Home Assistant

hass-pfsense Join pfSense with home-assistant! hass-pfsense uses the built-in xmlrpc service of pfSense for all interactions. No special plugins or so

Travis Glenn Hansen 105 Dec 24, 2022
This is a Client-Server-System which can share the screen from the server to client and in the other direction.

Screenshare-Streaming-Python This is a Client-Server-System which can share the screen from the server to client and in the other direction. You have

VFX / Videoeffects Creator 1 Nov 19, 2021
📨 Share files easily over your local network from the terminal! 📨

Fileshare 📨 Share files easily over your local network from the terminal! 📨 Installation #

Dopevog 11 Sep 10, 2021
Dark Utilities - Cloudflare Uam Bypass

Dark Utilities - Cloudflare Uam Bypass

Inplex-sys 26 Dec 14, 2022
A python shell / chat bot for XMPP and cloud services

XMPP_Shell_Bot A python shell / chat bot for XMPP and cloud services, designed for penetration testers to bypass network filters. To better understand

Abdulkareem Aldeek 1 Jan 09, 2022
A library of functions that can be used to manage the download of claims from the LBRY network.

lbrytools A library of functions that can be used to manage the download of claims from the LBRY network. It includes methods to download claims by UR

13 Dec 03, 2022
Distribute a portion of your yield to other addresses 💙

YSHARE Distribute a portion of your yield to other addresses. How does it work Desposit your yToken or tokens into this contract Set the benificiaries

11 Nov 24, 2021
IP Rover - An Excellent OSINT tool to get information of any ip address

IP Rover - An Excellent OSINT tool to get information of any ip address. All details are explained in below screenshot

Saad 20 Dec 16, 2022
Ping Verification Python Script

Python Script Port Scanner Script WHAT IS IT? Port scanner script using Python. HOW IT WORKS Once the script has been executed, it will request the ta

AC 0 Dec 12, 2021
Visualize the electric field of a point charge network.

ElectriPy ⚡ Visualize the electric field of a point charges network. 🔌 Installation Install ElectriPy package: $ pip install electripy You are all d

Dylan Tintenfich 29 Aug 29, 2022
Home Assistant integration for MyEnergi devices

myenergi for Home Assistant myenergi custom component for Home Assistant This is a very early release, will add more documentations soon! This compone

Johan Isacsson 70 Dec 18, 2022
A transport agnostic sync/async RPC library that focuses on exposing services with a well-defined API using popular protocols.

WARNING: This is from spyne's development branch. This version is not released yet! Latest stable release can be found in the 2_13 branch. If you like

1.1k Dec 23, 2022
School Project using Python Sockets and Personal Encryption Method.

Python-Secure-File-Transfer School Project using Python Sockets and Personal Encryption Method. Installation Must have python3 installed on your syste

1 Dec 03, 2021
pyWhisker is a Python equivalent of the original Whisker made by Elad Shamir and written in C#.

PyWhisker pyWhisker is a Python equivalent of the original Whisker made by Elad Shamir and written in C#. This tool allows users to manipulate the msD

Shutdown 325 Jan 08, 2023
批量检查目标是否为cdn

🐸 Frog For Automatic Scan 🐶 Doge For Defense Evasion&Offensive Security Frog-checkCDN 批量检查目标是否为cdn Usage: python3 checkCDN.py list.txt list内可以为ip或者d

TimWhite 119 Dec 27, 2022
The Delegate Network: An Interactive Voice Response Delegative Democracy Implementation of Liquid Democracy

The Delegate Network Overview The delegate network is a completely transparent, easy-to-use and understand version of what is sometimes called liquid

James Bowery 2 Feb 25, 2022
Blockchain-Enabled IoT Sensor Framework that uses Augmented Reality and Artificial Intelligence.

Arduino + Raspberry Pi + Unity3D + Cloud + Hyperledger Our Mission: Keep it simple, leave no one behind. Blockchain-Enabled Smart Sensor Framework usi

DappAR 23 Dec 05, 2021
IP Pinger - This tool allows you to enter an IP and check if its currently connected to a host

IP Pinger - This tool allows you to enter an IP and check if its currently connected to a host

invasion 3 Feb 18, 2022
CORS Bypass Proxy Cloud Function

CORS Bypass Proxy Cloud Function

Elayamani K 1 Oct 23, 2021