Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Allow Black to use a different Python version than Vim #4245

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

### Integrations

<!-- For example, Docker, GitHub Actions, pre-commit, editors -->
- Allow Black to use a different Python version than Vim

### Documentation

Expand Down
40 changes: 31 additions & 9 deletions autoload/black.vim
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import os
import sys
import vim

from pathlib import Path

def strtobool(text):
if text.lower() in ['y', 'yes', 't', 'true', 'on', '1']:
return True
Expand Down Expand Up @@ -34,7 +36,7 @@ FLAGS = [
]


def _get_python_binary(exec_prefix, pyver):
def _get_python_binary(exec_prefix, pyver=sys.version_info[:3]):
try:
default = vim.eval("g:pymode_python").strip()
except vim.error:
Expand All @@ -51,39 +53,52 @@ def _get_python_binary(exec_prefix, pyver):
exec_path = (bin_path / f"python3").resolve()
if exec_path.exists():
return exec_path
# TODO: Instead of failing, try to find black in PATH first and use its
# exec_prefix. This can happen when black is installed systemwide and
# g:black_virtualenv was not updated.
raise ValueError("python executable not found")

def _get_python_version(exec_path, as_string=False):
import subprocess
version_proc = subprocess.run([exec_path, "--version"], stdout=subprocess.PIPE, text=True)
version = version_proc.stdout.split(" ")[1].strip()
if not as_string:
version = tuple(map(int, version.split(".")))
return version

def _get_pip(venv_path):
if sys.platform[:3] == "win":
return venv_path / 'Scripts' / 'pip.exe'
return venv_path / 'bin' / 'pip'

def _get_virtualenv_site_packages(venv_path, pyver):
def _get_virtualenv_site_packages(venv_path):
if sys.platform[:3] == "win":
return venv_path / 'Lib' / 'site-packages'
return venv_path / 'lib' / f'python{pyver[0]}.{pyver[1]}' / 'site-packages'
venv_python_version = _get_python_version(_get_python_binary(venv_path))
return venv_path / 'lib' / f'python{venv_python_version[0]}.{venv_python_version[1]}' / 'site-packages'

def _initialize_black_env(upgrade=False):
virtualenv_path = Path(vim.eval("g:black_virtualenv")).expanduser()
virtualenv_site_packages = str(_get_virtualenv_site_packages(virtualenv_path))
if vim.eval("g:black_use_virtualenv ? 'true' : 'false'") == "false":
if upgrade:
print("Upgrade disabled due to g:black_use_virtualenv being disabled.")
print("Either use your system package manager (or pip) to upgrade black separately,")
print("or modify your vimrc to have 'let g:black_use_virtualenv = 1'.")
return False
else:
# Nothing needed to be done.
if virtualenv_site_packages not in sys.path:
sys.path.insert(0, virtualenv_site_packages)
return True

# TODO: Is this check about the Vim plugin, or about Black?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's about Black primarily; Black itself requires 3.8+ (since Python 3.7 is no longer supported). By default I would want the same version requirement for the Vim plugin, but if there's some reason to keep supporting older versions in the plugin code, I'm open to it.

pyver = sys.version_info[:3]
if pyver < (3, 8):
print("Sorry, Black requires Python 3.8+ to run.")
return False

from pathlib import Path
import subprocess
import venv
virtualenv_path = Path(vim.eval("g:black_virtualenv")).expanduser()
virtualenv_site_packages = str(_get_virtualenv_site_packages(virtualenv_path, pyver))
first_install = False
if not virtualenv_path.is_dir():
print('Please wait, one time setup for Black.')
Expand Down Expand Up @@ -120,7 +135,12 @@ def _initialize_black_env(upgrade=False):
return True

if _initialize_black_env():
import black
# TODO: Better handling of the case when import succeeds but Black doesn't
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this comment; if Black is uninstalled, surely the import would fail?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, not always. This can be triggered by Vim built with Py3.12 importing black from env/lib/python3.11/site-packages, which this PR makes possible.
If black is uninstalled after it was used once in such a setup, then pip uninstall leaves site-packages/black/__pycache__/ behind and that causes import black to succeed:

$ env/bin/python3 -c "import black; print(dir(black)); print(black.__file__)"
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
None

It's a corner case of a corner case, but it's a pain to troubleshoot when it bites.

# work, such as when it has been uninstalled from the virtualenv
try:
import black
except ImportError:
print(f"Could not import black from any of: {', '.join(sys.path)}.")
import time

def get_target_version(tv):
Expand Down Expand Up @@ -217,7 +237,9 @@ def BlackUpgrade():
_initialize_black_env(upgrade=True)

def BlackVersion():
print(f'Black, version {black.__version__} on Python {sys.version}.')
virtualenv_path = Path(vim.eval("g:black_virtualenv")).expanduser()
virtualenv_python_version = _get_python_version(_get_python_binary(virtualenv_path), as_string=True)
print(f'Black, version {black.__version__} on Python {virtualenv_python_version}.')

EndPython3

Expand Down