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)
Owner
null
Test - Python project for Collection Server and API Server

QProjectPython Collection Server 와 API Server 를 위한 Python 프로젝트 입니다. [FastAPI참고]

null 1 Jan 3, 2022
This is a Client-Server-System which can share the screen from the server to client and in the other direction.

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

VFX / Videoeffects Creator 1 Nov 19, 2021
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 5, 2023
Passive TCP/IP Fingerprinting Tool. Run this on your server and find out what Operating Systems your clients are *really* using.

Passive TCP/IP Fingerprinting This is a passive TCP/IP fingerprinting tool. Run this on your server and find out what operating systems your clients a

Nikolai Tschacher 158 Dec 20, 2022
Pritunl is a distributed enterprise vpn server built using the OpenVPN protocol.

Pritunl is a distributed enterprise vpn server built using the OpenVPN protocol.

Pritunl 3.8k Jan 3, 2023
TicTacToe using Socket Server

TicTacToe using Socket Server This is a project for the class : 18CSC302J - Computer Networks by Dr. S.Babu Contributors Suvodeep Sinha RA191100301010

Suvodeep Sinha 12 Nov 30, 2022
An ftp syncing python package that I use to sync pokemon saves between my hacked 3ds running ftpd and my server

Sync file pairs over ftp and apply patches to them. Useful for using ftpd to transfer ROM save files to and from your DS if you also play on an emulator. Setup a cron job to check for your DS's ftp server periodically to setup automatic syncing. Untested on windows. It may just work out of the box, unsure though.

null 17 Jan 4, 2023
A SOCKS proxy server implemented with the powerful python cooperative concurrency framework asyncio.

asyncio-socks-server A SOCKS proxy server implemented with the powerful python cooperative concurrency framework asyncio. Features Supports both TCP a

Amaindex 164 Dec 30, 2022
A simple DHCP server and client simulation with python

About The Project This is a simple DHCP server and client simulation. I implemented it for computer network course spring 2021 The client can request

shakiba 3 Feb 8, 2022
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

null 4 Sep 13, 2022
Simple threaded Python Rickroll server. Listens on port 23 by default.

Terminal Rickroll Simple threaded Python Rickroll server. Listens on port 23 by default. Rickroll video made using Video-To-Ascii and the standard ric

AG 10 Sep 13, 2022
Python implementation of the Session open group server

API Documentation CLI Reference Want to build from source? See BUILDING.md. Want to deploy using Docker? See DOCKER.md. Installation Instructions Vide

Oxen 36 Jan 2, 2023
🎥 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 Calendar subscribe server for python

cn-holiday-ics-server A calendar subscribe server 直接使用我搭建的服务 订阅节假日:https://cdxy.fun:9999/holiday 订阅调休上班:https://cdxy.fun:9999/workday 节假日和调休上班在一起的版本:h

CD 11 Nov 12, 2022
TsuserverMoS - A Python-based server for Attorney Online,

tsuserverMoS A Python-based server for Attorney Online, forked from RealKaiser/tsuserverCC Requires Python 3.7+ and PyYAML. Changes/additions from tsu

null 1 Dec 30, 2021
Serves some data over HTTP, once. Based on the built-in Python module http.server

serve-me-once Serves some data over HTTP, once. Based on the built-in Python module http.server.

Peder Bergebakken Sundt 2 Jan 6, 2022
A simple multi-threaded time server and client in python.

time-server-client A simple multi-threaded time server and client in Python. This uses the latest match/case command found in Python 3.10 so requires

Zeeshan Mulk 1 Jan 29, 2022
BlueHawk is an HTTP/1.1 compliant web server developed in python

This project is done as a part of Computer Networks course. It aims at the implementation of the HTTP/1.1 Protocol based on RFC 2616 and includes the basic HTTP methods of GET, POST, PUT, DELETE and HEAD.

null 2 Nov 11, 2022
No-dependency, single file NNTP server library for developing modern, rfc3977-compliant (bridge) NNTP servers.

nntpserver.py No-dependency, single file NNTP server library for developing modern, rfc3977-compliant (bridge) NNTP servers for python >=3.7. Develope

Manos Pitsidianakis 44 Nov 14, 2022