1

hope you are all well.

Before stating problem, let me tell you my setup. I am using Emacs 27.1, using pyvenv, projectile, lsp-mode, lsp-python-ms for python-mode. I have set WORKON_HOME environmental variable to ~/.pyenv/versions and created an env named venv3.8. I can activate the venv using <M-x> pyvenv-workon <RET> venv3.8 <RET>. After that i can start lsp with lsp command.

My idea was to set virtualenv (using pyvenv-workon) automatically upon opening a project (python project) or opening a python script (not part of a project). So the concept was like this -

  1. If a project is opened, meaning projectile-project-name is available first check if there is a virtualenv with the same name, if thats available activate it. If thats not available check a special variable (which i set with defcustom) name python-env-name for a hint, now if the specified env is available then activate it. Else do nothing.
  2. If a standalone script is opened, then check if pyhton-env-name for a hint, if the specified env is available, activate it. Else do nothing.
  3. After activating the virtualenv, start lsp-mode.

The custom variable python-env-name is supposed to be available to set in a .dir-local.el so that i can easily set per project venv if necessary.

Here is my current code -

(defcustom python-env-name "venv"
  "Set the virtualenv name to be used.

The virtualenv name is the folder name of a virtualenv
located under WORKON_HOME directory.  It is set to be
~/.pyenv/versions."
  :type 'string
  :tag "Python Virtual Environment Name"
  :group 'python
  :safe 'stringp)

(defun my/log (message)
  "Log MESSAGE to *my-logs* buffer.

MESSAGE is string value."
  (interactive "sMessage: ")
  (if (stringp message)
      (with-current-buffer (get-buffer-create "*my-logs*")
        (insert message))))

(defun my/activate-venv-for-project ()
  "Activates virtualenv for current project."
  (let ((project-venv-path (concat (getenv "WORKON_HOME") "/"
                                   projectile-project-name))
        (default-venv-path (concat (getenv "WORKON_HOME") "/"
                                   (buffer-local-value 'python-env-name (current-buffer)))))
    (my/log (format "Project venv: %s, found: %s\n" project-venv-path
                    (file-directory-p project-venv-path)))
    (my/log (format "Default venv: %s, found: %s\n" default-venv-path
                    (file-directory-p default-venv-path)))
    (if (file-directory-p project-venv-path)
        (pyvenv-workon projectile-project-name)
      (if (file-directory-p default-venv-path)
          (pyvenv-workon python-env-name)))))

(defun my/activate-venv-for-script ()
  "Activate virtualenv for a standalone script."
  (let ((default-venv-path (concat (getenv "WORKON_HOME") "/"
                                   (buffer-local-value 'python-env-name (current-buffer)))))
    (my/log (format "Script default venv: %s, found: %s\n" default-venv-path
                    (file-directory-p default-venv-path)))
    (if (file-directory-p default-venv-path)
        (pyvenv-workon python-env-name))))
(defun my/setup-python-environment ()
  "Setup the python virtual env for project or python script.

This function first checks if we opened a project, if so either the
virtualenv named as the projectile-project-name or the value of
python-env-name will be used to activate virtualenv.  If none are
aviable no virtualenv will be activated.  If we didnt open any
project, then simply check the python-env-name variable, if
the env is available then activate it or not.  Afterwards it will
start up lsp-mode."
  (interactive)
  (hack-local-variables-apply)
  (hack-dir-local-variables)
  (if (bound-and-true-p projectile-project-name)
      (my/activate-venv-for-project)
    (my/activate-venv-for-script))
  (require 'lsp-python-ms)
  (lsp))

(add-hook 'python-mode-hook 'my/setup-python-environment)

As you can see, i tried to show some message while setting up the environment. Then i opened a python project (using projectile), and opened a python file. I have already set the python-env-name in a dir-local file as follows -

((python-mode . ((python-env-name . "venv3.8"))))

Here is the text from *my-logs* buffer -

Project venv: ~/.pyenv/versions/python-check, found: nil
Default venv: ~/.pyenv/versions/venv, found: nil

So clearly, the dir-local value is not used in the my/setup-python-environment. But if i put the cursor at the python file and run <M-x> eval-expression <RET> python-env-name <RET> it shows "venv3.8" in the minibuffer. So the dir local variables are read. What am i doing wrong? Please help. Forgive my if i am doing anything wrong as i am very new to elisp. Thanks in advance. I hope i was clear enough.

1

Local variables are set after hooks are run. Try using a timer:

(let ((buf (current-buffer)))
  (run-with-timer 0.01 nil
          (lambda ()
            (with-current-buffer buf
              (if (bound-and-true-p projectile-project-name)
                (my/activate-venv-for-project)
               (my/activate-venv-for-script))
              (require 'lsp-python-ms)
              (lsp)))))
| improve this answer | |
  • I tried the timer solution, but got an error saying buf is void variable. – shimon Oct 18 at 10:54
  • Is lexical binding enabled? – Erik Oct 18 at 11:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.