Pyright and Pylama for neovim Python development

Switching to pyright 🪨

It turned out that pylsp wasn't all that I thought it was, and I've switch to Microsoft's LSP, pyright. It's job is to do, in addition to a limited amount of linting (which I'll probably turn off), the static type-checking that I was using mypy for, except even better: finally match statement support! This left me to re-include null-ls for formatting with black and isort, and diagnostics with pylama. But there were a few problems.

Fixing null-ls and pylama 🦙

Firstly, the problem with pylama was that it only looks for a configuration file pylama.ini within the current working directory (cwd), or else a user configuration file ~/.pylama.ini. I have a feeling this does the job for many IDE whose linting program command runners always start at the project root, but the problem for me was two-fold: I'm not using an IDE and the working directory is a subfolder where the file I'm working on is; and null-ls seems to have its own thoughts on where to place the root/cwd.

To fix pylama, I forked it and changed how it looks for configuration files to having it traverse up the cwd parent tree until it reaches ~, looking for any config file along the way. I might want to change this to have it in general stop at the base of the nearest module, but then it wouldn't work for this particular project.

diff --git a/pylama/config.py b/pylama/config.py
index f946619..08b2a29 100644
--- a/pylama/config.py
+++ b/pylama/config.py
@@ -16,7 +16,8 @@
 DEFAULT_LINTERS = "pycodestyle", "pyflakes", "mccabe"
 
 CURDIR = Path.cwd()
-HOMECFG = Path.home() / ".pylama.ini"
+HOMEDIR = Path.home()
+HOMECFG = HOMEDIR / ".pylama.ini"
 CONFIG_FILES = "pylama.ini", "setup.cfg", "tox.ini", "pytest.ini"
 
 # Setup a logger
@@ -62,10 +63,13 @@ def get_default_config_file(rootdir: Path = None) -> Optional[str]:
     if rootdir is None:
         return DEFAULT_CONFIG_FILE
 
-    for filename in CONFIG_FILES:
-        path = rootdir / filename
-        if path.is_file() and os.access(path, os.R_OK):
-            return path.as_posix()
+    while HOMEDIR in rootdir.parents:
+        for filename in CONFIG_FILES:
+            path = rootdir / filename
+            if path.is_file() and os.access(path, os.R_OK):
+                return path.as_posix()
+        rootdir = rootdir.parent
+
 
     return None

Running pylama from the terminal while in a subdirectory now finds the project config, hooray! But it didn't work within nvim; the problem was that null-ls gives its sources a particular root directory, and in this case it was giving the repository directory project, and not the path of the current working directory, nor the Python project project/python. I haven't figured out where exactly it's choosing to do this, or why, but it's probably to do with the git root. To fix this we thankfully don't need to go forking null-ls and changing things, but instead provide an override to cwd for the pylama source in our null-ls config, and the simplest is just to provide the neovim evenloop cwd.

local null_ls = require("null-ls")
local diagnostics = null_ls.builtins.diagnostics
local formatting = null_ls.builtins.formatting

null_ls.setup({
    sources = {
        -- Python
        diagnostics.pylama.with({
            -- NOTE: for some reason pylama wants to start at the repository root?
            -- this means it'll miss a pylama.ini in a nested directory
            -- e.g. project/python/pylama.ini
            cwd = vim.loop.cwd,
        }),
        formatting.black,
        formatting.isort,
    },
})

And now everything works just the way I've wanted it to!