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)
Some files casually made by @AneekBiswas

Python-Tools All Pyhthon Files are created and managed by @AneekBiswas Modules needed to be downloaded 1.CLI bagels.py random guess.py random text-tow

1 Feb 23, 2022
This is a Client-Server-System which can send audio from a microphone from the server to client and in the other direction.

Audio-Streaming-Python This is a Client-Server-System which can send audio from a microphone from the server to client and in the other direction. You

VFX / Videoeffects Creator 0 Jan 05, 2023
Jogo da forca simples com conexão entre cliente e servidor utilizando TCP.

JogoDaForcaTCP Um jogo da forca simples com conexão entre cliente e servidor utilizando o protocólo TCP. Como jogar: Habilite a porta 20000, inicie o

Kelvin Santos 1 Dec 01, 2021
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
Pywbem - A WBEM client and related utilities, written in pure Python.

Pywbem - A WBEM client and related utilities, written in pure Python Overview Pywbem is a WBEM client and WBEM indication listener and provides relate

PyWBEM Projects 39 Dec 22, 2022
9SPY: a Windows RAT built in Python using sockets

9SPY 👁‍🗨 9SPY is a Windows RAT built in Python using sockets Features Features will be listed here soon, there are currenly 14 Information This is a

doop 12 Dec 01, 2022
sync application configuration and settings across multiple multiplatform devices

sync application configuration and settings across multiple multiplatform devices ✨ Key Features • ⚗️ Installation • 📑 How To Use • 🤔 FAQ • 🛠️ Setu

Souvik 6 Aug 25, 2022
🎥 PYnema is a simple UDP server written in python, allows you to watch downloaded videos.

🎥 PYnema is a simple UDP server written in python, allows you to watch downloaded videos.

Jan Kupczyk 1 Jan 16, 2022
A simple Tor switcher script switches tor nodes in interval of time

Tor_Switcher A simple Tor switcher script switches tor nodes in interval of time This script will switch tor nodes in every interval of time that you

d4rk sh4d0w 2 Nov 15, 2021
Wifijammer - Continuously jam all wifi clients/routers

wifijammer Continuously jam all wifi clients and access points within range. The effectiveness of this script is constrained by your wireless card. Al

Dan McInerney 3.5k Dec 31, 2022
A Python framework for interacting with Solana's Pyth network.

Pyth Network A basic Python framework for reading and decoding data regarding the Pyth network

1 Nov 29, 2021
A great python/java dynamic DNS service for NameSilo, with log, email reminder...

English NameSilo DDNS is a DDNS service for NameSilo domain names for home broadband , it can automatically detect IP changes in home broadband

云牧青 77 Dec 28, 2022
A simple electrical network analyzer, BASED ON computer-aided design.

Electrical Network Analyzer A simple electrical network analyzer. Given the oriented graph of the electrical network (circut), BASED ON computer-aided

Ahmad Abdulrahman 4 Oct 15, 2022
A fully automated, accurate, and extensive scanner for finding log4j RCE CVE-2021-44228

log4j-scan A fully automated, accurate, and extensive scanner for finding vulnerable log4j hosts Features Support for lists of URLs. Fuzzing for more

FullHunt 3.2k Jan 02, 2023
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
A Python3 discord trojan, utilizing discord webhooks for sending information.

Vape-Lite-RAT A Python3 discord trojan, utilizing discord webhooks for sending information. What you do with this code / project / idea is non of my b

NightTab 12 Oct 15, 2022
Control your Puffco Peak Pro from your computer!

PuffcoPC Control your Puffco Peak Pro from your computer! Contributions Pull requests are welcome. For major changes, please open an issue first to di

Bryan Muschter 5 Nov 02, 2022
订阅转换,添加免流host

普通订阅转免流订阅 原理 将原来的订阅解析后添加免流host 使用方法 服务器域名/&&订阅链接&&免流host&&转换后服务器前缀 我这里已经在服务器上搭建好了

163 Apr 01, 2022
Python Scrcpy Client - allows you to view and control android device in realtime

Python Scrcpy Client This package allows you to view and control android device in realtime. Note: This gif is compressed and experience lower quality

LengYue 126 Jan 02, 2023
Caching for HTTPX

Caching for HTTPX. Note: Early development / alpha, use at your own risk. This package adds caching functionality to HTTPX Adapted from Eric Larson's

Mehul Solanki 0 Oct 08, 2022