Repository: palantir/python-language-server
Pull request #821 (2020-06-20 21:49:58)

Add configurable flake8 executable
The context for this change:
- I'm using virtural envs with different versions of `Python` so each of them has their own install of `pyls` and dependencies.
- I'm using `Sublime Text` with [`Sublime LSP`](https://github.com/sublimelsp/LSP) configured to use the `pyls` from the project's virtual env.
    <details>
    <summary>Example configuration</summary>

    ```json
    {
      "settings": {
        "LSP": {
          "pyls": {
            "enabled": true,
            "command": [
              "/virtualenvs/important-project/bin/pyls"
            ],
            "settings": {
              "pyls": {
                "plugins": {
                  "flake8": {
                    "enabled": true,
                    "config": "/work/code/important-project/.flake8"
                  }
                }
              }
            }
          }
        }
      }
    }
    ```
    </details>

Within that context, the `flake8_lint` plugin tries to start `flake8` via a process; either directly or as a module. In both cases, this happens outside of the virtual env so it does not pick up the correct `flake8` executable or module.

This change allows for adding an optional explicit `executable` configuration for `flake8` that can point directly to the virtual env's installed version.

This change should be backwards compatible as it defaults to the previous behavior when there is no `executable` provided in the configuration.

The other plugin's do not have a similar issue as they do not use a separate process.

palantirtech: Thanks for your interest in [palantir/python-language-server](https://github.com/palantir/python-language-server), @ruhulio! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.

Patch:
diff --git a/pyls/plugins/flake8_lint.py b/pyls/plugins/flake8_lint.py
index 4eae537..addb82c 100644
--- a/pyls/plugins/flake8_lint.py
+++ b/pyls/plugins/flake8_lint.py
@@ -41,25 +41,28 @@ def pyls_lint(workspace, document):
         log.debug("using flake8 with config: %s", opts['config'])
 
     # Call the flake8 utility then parse diagnostics from stdout
+    flake8_executable = settings.get('executable', 'flake8')
+
     args = build_args(opts, document.path)
-    output = run_flake8(args)
+    output = run_flake8(flake8_executable, args)
     return parse_stdout(document, output)
 
 
-def run_flake8(args):
+def run_flake8(flake8_executable, args):
     """Run flake8 with the provided arguments, logs errors
     from stderr if any.
     """
     # a quick temporary fix to deal with Atom
     args = [(i if not i.startswith('--ignore=') else FIX_IGNORES_RE.sub('', i))
             for i in args if i is not None]
-    log.debug("Calling flake8 with args: '%s'", args)
+
+    log.debug("Calling %s with args: '%s'", flake8_executable, args)
     try:
-        cmd = ['flake8']
+        cmd = [flake8_executable]
         cmd.extend(args)
         p = Popen(cmd, stdout=PIPE, stderr=PIPE)
     except IOError:
-        log.debug("Can't execute flake8. Trying with 'python -m flake8'")
+        log.debug("Can't execute %s. Trying with 'python -m flake8'", flake8_executable)
         cmd = ['python', '-m', 'flake8']
         cmd.extend(args)
         p = Popen(cmd, stdout=PIPE, stderr=PIPE)


Test patch:
diff --git a/test/plugins/test_flake8_lint.py b/test/plugins/test_flake8_lint.py
index 7c238be..778a58e 100644
--- a/test/plugins/test_flake8_lint.py
+++ b/test/plugins/test_flake8_lint.py
@@ -65,3 +65,18 @@ def test_flake8_config_param(workspace):
         call_args = popen_mock.call_args.args[0]
         assert 'flake8' in call_args
         assert '--config={}'.format(flake8_conf) in call_args
+
+
+def test_flake8_executable_param(workspace):
+    with patch('pyls.plugins.flake8_lint.Popen') as popen_mock:
+        mock_instance = popen_mock.return_value
+        mock_instance.communicate.return_value = [bytes(), bytes()]
+
+        flake8_executable = '/tmp/flake8'
+        workspace._config.update({'plugins': {'flake8': {'executable': flake8_executable}}})
+
+        _name, doc = temp_document(DOC, workspace)
+        flake8_lint.pyls_lint(workspace, doc)
+
+        call_args = popen_mock.call_args.args[0]
+        assert flake8_executable in call_args