2

We want to deliver a ready-to-deploy venv for different versions of our software.

Therefore, I wanted to write a simple Python script, which calls a couple of bash commands for installing every package we need.

So the steps are:

  1. Create a venv with a Name suffixed with version number
  2. Activate this venv
  3. Install packages from PyPy
  4. cd into a couple of local packages folders
  5. Install them by git install .
  6. cp a couple of files in the venv folder

My problem now is, I can't find a way to activate the venv from within the script. I already read about how it was done in Python 2 (with execfile) and about the exec(open(path_to_env_activate).read()) in Python 3.

However, the second version for Python3 gives me a Syntax Error:

Traceback (most recent call last):
  File "build.py", line 32, in <module>
    exec(open(venv_activate).read())
  File "<string>", line 1
    @echo off
            ^
SyntaxError: invalid syntax

What did I get wrong here? Or is there kinda best practice how to install venv with packages with a script?

Igl3
  • 4,900
  • 5
  • 35
  • 69
  • Consider using the [API of the `venv` module](https://docs.python.org/3/library/venv.html) directly to create and interact with virtual environments. As for activating a venv from within Python, that does not make any sense since activating a venv is supposed to put the current shell into a virtual sandbox, which simply doesn’t make sense when you are already using a non-virtual Python to execute it. Btw. the error you see is because you are trying to execute a batch file with Python. – poke Jul 18 '17 at 08:49
  • 1
    Okay, extending the EnvBuilder seems useful to me. But for now I need some kind of a quick and dirty solution and this seemed like the easiest way to do it. Anyway, is there a way to activate a venv from a script after all? – Igl3 Jul 18 '17 at 09:19
  • You can definitely activate a venv from within python. venv just sets up some environment tricks to influence the python module loader. You can influence the module loader in in the same fashion from inside the process by manipulating sys.path etc early in the process. HOWEVER this is somewhat unwise, because you can end up with a mix of modules from the system vs the venv if you aren't very careful. – jrodman Oct 15 '22 at 23:21

1 Answers1

0

The solution I found to this problem was using the Venv module to create the environment, combined with code from the activate_this.py module in Virtualenv. The module's source code: https://github.com/pypa/virtualenv/blob/main/src/virtualenv/activation/python/activate_this.py

This solution has worked well for me. I use it to generate environments and install packages as described in json template files.

Here is my venv_controller:

import venv
import os
import sys
import site
from pathlib import Path

class VenvController:
    
    def __init__(self): 
        self.venv_name = 'venv'
        self.base = str(Path().absolute() / self.venv_name)
        self.bin_dir = str(Path().absolute() / self.venv_name / 'bin')
    
    def create(self, project_name):
        # Create venv
        venv.create(
                self.base,
                prompt=f'arkistak-{project_name}',
                with_pip=True
        )

        
    def activate(self):
        
        # Only activating if venv created
        if not os.path.exists(self.base):
            return False
        
        # Activate new environment. refer to 
        # https://github.com/pypa/virtualenv/blob/main
        # /src/virtualenv/activation/python/activate_this.py
        os.environ['PATH'] = os.pathsep.join([self.bin_dir] + 
                os.environ.get("PATH", "").split(os.pathsep))
        os.environ['VIRTUAL_ENV'] = self.base
        prev_length = len(sys.path)
        for lib in "__LIB_FOLDERS__".split(os.pathsep):
            path = os.path.realpath(os.path.join(self.bin_dir, lib))
            site.addsitedir(path)

        sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]
        sys.real_prefix = sys.prefix
        sys.prefix = self.base
        return True
bajaco
  • 830
  • 1
  • 4
  • 11