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)
PoC code for stealing the WiFi password of a network with a Lovebox IOT device connected

LoveBoxer PoC code for stealing the WiFi password of a network with a Lovebox IOT device connected. This PoC was is what I used in this blogpost Usage

Graham Helton 10 May 24, 2022
A repo with study material, exercises, examples, etc for Devnet SPAUTO

MPLS in the SDN Era -- DevNet SPAUTO All of the study notes have now been moved to use auto-generated documentation to build a static site with Githu

Hugo Tinoco 67 Nov 16, 2022
This Python script can be used to bypass IP source restrictions using HTTP headers.

ipsourcebypass This Python script can be used to bypass IP source restrictions using HTTP headers. Features 17 HTTP headers. Multithreading. JSON expo

Podalirius 322 Dec 28, 2022
pyngrok is a Python wrapper for ngrok

pyngrok is a Python wrapper for ngrok that manages its own binary, making ngrok available via a convenient Python API.

Alex Laird 329 Dec 31, 2022
A script for generating WireGuard configs from Surfshark VPN

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

Alireza Ahmand 58 Dec 23, 2022
User-friendly packet captures

capture-packets: User-friendly packet captures Please read before using All network traffic occurring on your machine is captured (unless you specify

Seth Michael Larson 2 Feb 05, 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
Find information about an IP address, such as its location, ISP, hostname, region, country, and city.

Find information about an IP address, such as its location, ISP, hostname, region, country, and city. An IP address can be traced, tracked, and located.

Sachit Yadav 2 Jul 09, 2022
Python Scripts for Cisco Identity Services Engine (ISE)

A set of Python scripts to configure a freshly installed Cisco Identity Services Engine (ISE) for simple operation; in my case, a basic Cisco Software-Defined Access environment.

Roddie Hasan 9 Jul 19, 2022
simple subdomain finder

Subdomain-finder Simple SubDomain finder using python which is easy to use just download and run it Wordlist you can use your own wordlist but here i

AsjadOwO 5 Sep 24, 2021
DNSStager is an open-source project based on Python used to hide and transfer your payload using DNS.

What is DNSStager? DNSStager is an open-source project based on Python used to hide and transfer your payload using DNS. DNSStager will create a malic

Askar 547 Dec 20, 2022
A transport agnostic sync/async RPC library that focuses on exposing services with a well-defined API using popular protocols.

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

1.1k Dec 23, 2022
A simple GitHub Action that physically puts your senses on alert when your build/release fails

GH Release Paniker A simple GitHub Action that physically puts your senses on alert when your build/release fails Usage Requirements: Raspberry Pi, LE

Hemanth Krishna 5 Dec 20, 2021
TradingView Interactive Brokers Integration using Webhooks

TradingView Interactive Brokers Integration using Webhooks

84 Dec 19, 2022
Ctech Didik Auto Script VPN 👨🏻‍💻Youtube: Ctech Didik

CTech Didik Auto Script VPN SUPPORT OPERATING SYSTEM Debian GNU/Linux 11 (Bullseye) Debian GNU/Linux 10 (Buster) Debian GNU/Linux 9 (Stretch) Ubuntu S

Ctech Didik 27 Dec 20, 2022
A protocol or procedure that connects an ever-changing IP address to a fixed physical machine address

p0znMITM ARP Poisoning Tool What is ARP? Address Resolution Protocol (ARP) is a protocol or procedure that connects an ever-changing IP address to a f

Furkan OZKAN 9 Sep 18, 2022
This tool is for finding more detailed information of an IP Address.

This tool is for finding more detailed information of an IP Address.

3 Oct 08, 2021
Nyx-Net: Network Fuzzing with Incremental Snapshots

Nyx-Net: Network Fuzzing with Incremental Snapshots Nyx-Net is fast full-VM snapshot fuzzer for complex network based targets. It's built upon kAFL, R

Chair for Sys­tems Se­cu­ri­ty 146 Dec 16, 2022
Socket programming is a way of connecting two nodes on a network to communicate with each other

Socket Programming in Python Socket programming is a way of connecting two nodes on a network to communicate with each other. One socket(node) listens

Janak raikhola 1 Jul 05, 2022
Real-time text-editor using python tcp socket

Real-time text-editor using python tcp socket This project does not need any external libraries so you don't need to use virtual environments. All you

MatiYo 3 Aug 05, 2022