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)
ProxyBroker is an open source tool that asynchronously finds public proxies from multiple sources and concurrently checks them

ProxyBroker is an open source tool that asynchronously finds public proxies from multiple sources and concurrently checks them. Features F

Denis 3.2k Jan 04, 2023
Very simple FTP client, sync folder to FTP server, use python, opensource

ftp-sync-python Opensource, A way to safe your data, avoid lost data by Virus, Randsomware Some functions: Upload a folder automatically to FTP server

4 Sep 13, 2022
Mass Reverse IP Dibuat Dengan Python 3 Dan Ada Fitur Filter.

Reverse IP Tools Description. Reverse IP is a method to map an IP address to a sub domain. This tool is made in the python 3 programming language. Fea

Wan Naz ID 6 Oct 24, 2022
Official ProtonVPN Linux app

ProtonVPN Linux App Copyright (c) 2021 Proton Technologies AG This repository holds the ProtonVPN Linux App. For licensing information see COPYING. Fo

ProtonVPN 288 Jan 01, 2023
GlokyPortScannar is a really fast tool to scan TCP ports implemented in Python.

GlokyPortScannar is a really fast tool to scan TCP ports implemented in Python. Installation: This program requires Python 3.9. Linux

gl0ky 5 Jun 25, 2022
Repo for investigation of timeouts that happens with prolonged training on clients

Flower-timeout Repo for investigation of timeouts that happens with prolonged training on clients. This repository is meant purely for demonstration o

1 Jan 21, 2022
Simple Port Scanner With Socket Module In Python 3x

PortScanner Simple Port Scanner With Socket Module In Python 3x How To Install Requirements Of This Port Scanner sudo apt install python3;sudo apt ins

1 Nov 23, 2021
This is a top level socket library, making servers and clients EASY!

quick-net Sockets don't have to be a pain That's the motto this library was built with, and that's exactly what we made! This is a top-level socket li

Nate the great 15 Dec 17, 2021
Godzilla traffic decoder Godzilla Decoder 是一个用于 哥斯拉Godzilla 加密流量分析的辅助脚本。

Godzilla Decoder 简介 Godzilla Decoder 是一个用于 哥斯拉Godzilla 加密流量分析的辅助脚本。 Godzilla Decoder 基于 mitmproxy,是mitmproxy的addon脚本。 目前支持 哥斯拉3.0.3 PhpDynamicPayload的

He Ruiliang 40 Dec 25, 2022
OptiPLANT is a cloud-based based system that empowers professional and non-professional data scientists to build high-quality predictive models

OptiPLANT OptiPLANT is a cloud-based based system that empowers professional and non-professional data scientists to build high-quality predictive mod

Intellia ICT 1 Jan 26, 2022
Impacket is a collection of Python classes for working with network protocols.

What is Impacket? Impacket is a collection of Python classes for working with network protocols. Impacket is focused on providing low-level programmat

SecureAuth Corporation 10.4k Jan 09, 2023
Aiotor - a pool of proxies, shifting on each request

Aiotor - a pool of proxies, shifting on each request

Leon 32 Dec 26, 2022
批量检查目标是否为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
IoT owl is light face detection and recognition system made for small IoT devices like raspberry pi.

IoT Owl IoT owl is light face detection and recognition system made for small IoT devices like raspberry pi. Versions Heavy with mask detection withou

Ret2Me 6 Jun 06, 2022
A working cloudflare uam bypass !!

Dark Utilities - Cloudflare Uam Bypass Our Website https://over-spam.space/ ! Additional Informations The proxies type are http,https ... You need fas

Inplex-sys 26 Dec 14, 2022
Nautobot is a Network Source of Truth and Network Automation Platform.

Nautobot is a Network Source of Truth and Network Automation Platform. Nautobot was initially developed as a fork of NetBox (v2.10.4). Nautobot runs as a web application atop the Django Python framew

Nautobot 549 Dec 31, 2022
Remote vanilla PDB (over TCP sockets) done right: no extras, proper handling around connection failures and CI.

Overview docs tests package Remote vanilla PDB (over TCP sockets) done right: no extras, proper handling around connection failures and CI. Based on p

Ionel Cristian Mărieș 227 Dec 27, 2022
Build surface water network for MODFLOW's SFR Package

Surface water network Creates surface water network, which can be used to create MODFLOW's SFR. Python packages Python 3.6+ is required. Required geop

Mike Taves 20 Nov 22, 2022
Library containing the core modules for the kingdom-python-server.

🏰 Kingdom Core Library containing the core modules for the kingdom-python-server. Installation Use the package manager pip to install kingdom-core. p

T10 4 Dec 27, 2021
A script for generating WireGuard configs from Surfshark VPN

Surfshark WireGuard A script for generating WireGuard configs from Surfshark VPN. You must have python3 available on your machine. Usage Currently we

Alireza Ahmand 58 Dec 23, 2022