Skip to content

Make pyvenv style virtual environments easier to configure when embedding Python #66409

Description

@grahamd
mannequin
BPO 22213
Nosy @ncoghlan, @pitrou, @vstinner, @methane, @ericsnowcurrently, @zooba, @ndjensen, @LeslieGerman, @M-Kerr, @abrunner73
Dependencies
  • bpo-22257: PEP 432 (PEP 587): Redesign the interpreter startup sequence
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2014-08-17.11:42:41.318>
    labels = ['type-feature', '3.8']
    title = 'Make pyvenv style virtual environments easier to configure when embedding Python'
    updated_at = <Date 2021-12-08.21:26:34.716>
    user = 'https://bugs.python.org/grahamd'

    bugs.python.org fields:

    activity = <Date 2021-12-08.21:26:34.716>
    actor = 'ndjensen'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = []
    creation = <Date 2014-08-17.11:42:41.318>
    creator = 'grahamd'
    dependencies = ['22257']
    files = []
    hgrepos = []
    issue_num = 22213
    keywords = []
    message_count = 31.0
    messages = ['225434', '225436', '225437', '225739', '225742', '225771', '225774', '225890', '334926', '334948', '335015', '335468', '335470', '335479', '335484', '335648', '335650', '335688', '335692', '335749', '336793', '343636', '352905', '354856', '354857', '354858', '361600', '361869', '362260', '366570', '384496']
    nosy_count = 13.0
    nosy_names = ['ncoghlan', 'pitrou', 'vstinner', 'pyscripter', 'grahamd', 'methane', 'eric.snow', 'steve.dower', 'Henning.von.Bargen', 'ndjensen', 'Leslie', 'M.Kerr', 'abrunner73']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = None
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue22213'
    versions = ['Python 3.8']

    Linked PRs

    Activity

    1. grahamd commented on Aug 17, 2014

      grahamdmannequin
      MannequinAuthor

      In am embedded system, as the 'python' executable is itself not run and the Python interpreter is initialised in process explicitly using PyInitialize(), in order to find the location of the Python installation, an elaborate sequence of checks is run as implemented in calculate_path() of Modules/getpath.c.

      The primary mechanism is usually to search for a 'python' executable on PATH and use that as a starting point. From that it then back tracks up the file system from the bin directory to arrive at what would be the perceived equivalent of PYTHONHOME. The lib/pythonX.Y directory under that for the matching version X.Y of Python being initialised would then be used.

      Problems can often occur with the way this search is done though.

      For example, if someone is not using the system Python installation but has installed a different version of Python under /usr/local. At run time, the correct Python shared library would be getting loaded from /usr/local/lib, but because the 'python' executable is found from /usr/bin, it uses /usr as sys.prefix instead of /usr/local.

      This can cause two distinct problems.

      The first is that there is no Python installation at all under /usr corresponding to the Python version which was embedded, with the result of it not being able to import 'site' module and therefore failing.

      The second is that there is a Python installation of the same major/minor but potentially a different patch revision, or compiled with different binary API flags or different Unicode character width. The Python interpreter in this case may well be able to start up, but the mismatch in the Python modules or extension modules and the core Python library that was actually linked can cause odd errors or crashes to occur.

      Anyway, that is the background.

      For an embedded system the way this problem was overcome was for it to use Py_SetPythonHome() to forcibly override what should be used for PYTHONHOME so that the correct installation was found and used at runtime.

      Now this would work quite happily even for Python virtual environments constructed using 'virtualenv' allowing the embedded system to be run in that separate virtual environment distinct from the main Python installation it was created from.

      Although this works for Python virtual environments created using 'virtualenv', it doesn't work if the virtual environment was created using pyvenv.

      One can easily illustrate the problem without even using an embedded system.

      $ which python3.4
      /Library/Frameworks/Python.framework/Versions/3.4/bin/python3.4
      
      $ pyvenv-3.4 py34-pyvenv
      
      $ py34-pyvenv/bin/python
      Python 3.4.1 (v3.4.1:c0e311e010fc, May 18 2014, 00:54:21)
      [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
      Type "help", "copyright", "credits" or "license" for more information.
      >>> import sys
      >>> sys.prefix
      '/private/tmp/py34-pyvenv'
      >>> sys.path
      ['', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin', '/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload', '/private/tmp/py34-pyvenv/lib/python3.4/site-packages']
      
      $ PYTHONHOME=/tmp/py34-pyvenv python3.4
      Fatal Python error: Py_Initialize: unable to load the file system codec
      ImportError: No module named 'encodings'
      Abort trap: 6

      The basic problem is that in a pyvenv virtual environment, there is no duplication of stuff in lib/pythonX.Y, with the only thing in there being the site-packages directory.

      When you start up the 'python' executable direct from the pyvenv virtual environment, the startup sequence checks know this and consult the pyvenv.cfg to extract the:

      home = /Library/Frameworks/Python.framework/Versions/3.4/bin

      setting and from that derive where the actual run time files are.

      When PYTHONHOME or Py_SetPythonHome() is used, then the getpath.c checks blindly believe that is the authoritative value:

      • Step 2. See if the $PYTHONHOME environment variable points to the
      • installed location of the Python libraries. If $PYTHONHOME is set, then
      • it points to prefix and exec_prefix. $PYTHONHOME can be a single
      • directory, which is used for both, or the prefix and exec_prefix
      • directories separated by a colon.
          /* If PYTHONHOME is set, we believe it unconditionally */
          if (home) {
              wchar_t *delim;
              wcsncpy(prefix, home, MAXPATHLEN);
              prefix[MAXPATHLEN] = L'\0';
              delim = wcschr(prefix, DELIM);
              if (delim)
                  *delim = L'\0';
              joinpath(prefix, lib_python);
              joinpath(prefix, LANDMARK);
              return 1;
          }
      Because of this, the problem above occurs as the proper runtime directories for files aren't included in sys.path. The result being that the 'encodings' module cannot even be found.

      What I believe should occur is that PYTHONHOME should not be believed unconditionally. Instead there should be a check to see if that directory contains a pyvenv.cfg file and if there is one, realise it is a pyvenv style virtual environment and do the same sort of adjustments which would be made based on looking at what that pyvenv.cfg file contains.

      For the record this issue is affecting Apache/mod_wsgi and right now the only workaround I have is to tell people that in addition to setting the configuration setting corresponding to PYTHONHOME, to use configuration settings to have the same effect as doing:

      PYTHONPATH=/Library/Frameworks/Python.framework/Versions/3.4/lib/python34.zip:/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4:/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/plat-darwin:/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/lib-dynload

      so that the correct runtime files are found.

      I am still trying to work out a more permanent workaround I can add to mod_wsgi code itself since can't rely on a fix for existing Python versions with pyvenv support.

      Only other option is to tell people not to use pyvenv and use virtualenv instead.

      Right now I can offer no actual patch as that getpath.c code is scary enough that not even sure at this point where the check should be incorporated or how.

      Only thing I can surmise is that the current check for pyvenv.cfg being before the search for the prefix is meaning that it isn't consulted.

      /* Search for an environment configuration file, first in the
         executable's directory and then in the parent directory.
         If found, open it for use when searching for prefixes.
      */
      
      {
          wchar_t tmpbuffer[MAXPATHLEN+1];
          wchar_t *env_cfg = L"pyvenv.cfg";
          FILE * env_file = NULL;
      
          wcscpy(tmpbuffer, argv0_path);
      
              joinpath(tmpbuffer, env_cfg);
              env_file = _Py_wfopen(tmpbuffer, L"r");
              if (env_file == NULL) {
                  errno = 0;
                  reduce(tmpbuffer);
                  reduce(tmpbuffer);
                  joinpath(tmpbuffer, env_cfg);
                  env_file = _Py_wfopen(tmpbuffer, L"r");
                  if (env_file == NULL) {
                      errno = 0;
                  }
              }
              if (env_file != NULL) {
                  /* Look for a 'home' variable and set argv0_path to it, if found */
                  if (find_env_config_value(env_file, L"home", tmpbuffer)) {
                      wcscpy(argv0_path, tmpbuffer);
                  }
                  fclose(env_file);
                  env_file = NULL;
              }
          }
          pfound = search_for_prefix(argv0_path, home, _prefix, lib_python);
    2. ncoghlan commented on Aug 17, 2014

      @ncoghlan
      Contributor

      Yeah, PEP-432 (my proposal to redesign the startup sequence) could just as well be subtitled "getpath.c hurts my brain" :P

      One tricky part here is going to be figuring out how to test this - perhaps adding a new test option to _testembed and then running it both inside and outside a venv.

    3. ncoghlan commented on Aug 17, 2014

      @ncoghlan
      Contributor

      Graham pointed out that setting PYTHONHOME ends up triggering the same control flow through getpath.c as calling Py_SetPythonHome, so this can be tested just with pyvenv and a suitably configured environment.

      It may still be a little tricky though, since we normally run the pyvenv tests in isolated mode to avoid spurious failures due to bad environment settings...

    4. ncoghlan commented on Aug 23, 2014

      @ncoghlan
      Contributor

      Some more experiments, comparing an installed vs uninstalled Python. One failure mode is that setting PYTHONHOME just plain breaks running from a source checkout (setting PYTHONHOME to the checkout directory also fails):

      $ ./python -m venv --without-pip /tmp/issue22213-py35
      
      $ /tmp/issue22213-py35/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      /usr/local /usr/local
      
      $ PYTHONHOME=/usr/local /tmp/issue22213-py35/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      Fatal Python error: Py_Initialize: Unable to get the locale encoding
      ImportError: No module named 'encodings'
      Aborted (core dumped)

      Trying after running "make altinstall" (which I had previously done for 3.4) is a bit more enlightening:

      $ python3.4 -m venv --without-pip /tmp/issue22213-py34
      
      $ /tmp/issue22213-py34/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      /usr/local /usr/local
      
      $ PYTHONHOME=/usr/local /tmp/issue22213-py34/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      /usr/local /usr/local
      
      $ PYTHONHOME=/tmp/issue22213-py34 /tmp/issue22213-py34/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      Fatal Python error: Py_Initialize: Unable to get the locale encoding
      ImportError: No module named 'encodings'
      Aborted (core dumped)
      
      $ PYTHONHOME=/tmp/issue22213-py34:/usr/local /tmp/issue22213-py34/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      Fatal Python error: Py_Initialize: Unable to get the locale encoding
      ImportError: No module named 'encodings'
      Aborted (core dumped)
      [ncoghlan@lancre py34]$ PYTHONHOME=/usr/local:/tmp/issue22213-py34/bin /tmp/issue22213-py34/bin/python -c "import sys; print(sys.base_prefix, sys.base_exec_prefix)"
      /usr/local /tmp/issue22213-py34/bin

      I think what this is actually showing is that there's a fundamental conflict between mod_wsgi's expectation of being able to set PYTHONHOME to point to the virtual environment, and the way PEP-405 virtual environments actually work.

      With PEP-405, all the operations in getpath.c expect to execute while pointing to the *base* environment: where the standard library lives. It is then up to site.py to later adjust the based prefix location, as can be demonstrated by the fact pyvenv.cfg isn't processed if processing the site module is disabled:

      $ /tmp/issue22213-py34/bin/python -c "import sys; print(sys.prefix, sys.exec_prefix)"
      /tmp/issue22213-py34 /tmp/issue22213-py34
      $ /tmp/issue22213-py34/bin/python -S -c "import sys; print(sys.prefix, sys.exec_prefix)"
      /usr/local /usr/local

      At this point in time, there isn't an easy way for an embedding application to say "here's the standard library, here's the virtual environment with user packages" - it's necessary to just override the path calculations entirely.

      Allowing that kind of more granular configuration is one of the design goals of PEP-432, so adding that as a dependency here.

    5. grahamd commented on Aug 23, 2014

      grahamdmannequin
      MannequinAuthor

      It is actually very easy for me to work around and I released a new mod_wsgi version today which works.

      When I get a Python home option, instead of calling Py_SetPythonHome() with it, I append '/bin/python' to it and call Py_SetProgramName() instead.

    6. ncoghlan commented on Aug 24, 2014

      @ncoghlan
      Contributor

      Excellent! If I recall correctly, that works because we resolve the symlink when looking for the standard library, but not when looking for venv configuration file.

      I also suspect this is all thoroughly broken on Windows - there are so many configuration operations and platform specific considerations that need to be accounted for in getpath.c these days that it has become close to incomprehensible :(

      One of my main goals with PEP-432 is actually to make it possible to rewrite the path configuration code in a more maintainable way - my unofficial subtitle for that PEP is "getpath.c must die!" :)

    7. changed the title [-]pyvenv style virtual environments unusable in an embedded system[/-] [+]Make pyvenv style virtual environments easier to configure when embedding Python[/+] on Aug 24, 2014
    8. grahamd commented on Aug 24, 2014

      grahamdmannequin
      MannequinAuthor

      I only make the change to Py_SetProgramName() on UNIX and not Windows. This is because back in mod_wsgi 1.0 I did actually used to use Py_SetProgramName() but it didn't seem to work in sane way on Windows so changed to Py_SetPythonHome() which worked on both Windows and UNIX. Latest versions of mod_wsgi haven't been updated yet to even build on Windows, so not caring about Windows right now.

    9. pitrou commented on Aug 25, 2014

      @pitrou
      Member

      That workaround would definitely deserve being wrapped in a higher-level API invokable by embedding applications, IMHO.

    10. ncoghlan commented on Feb 6, 2019

      @ncoghlan
      Contributor

      (Added Victor, Eric, and Steve to the nosy list here, as I'd actually forgotten about this until issue bpo-35706 reminded me)

      Core of the problem: the embedding APIs don't currently offer a Windows-compatible way of setting up "use this base Python and this venv site-packages", and the way of getting it to work on other platforms is pretty obscure.

    11. zooba commented on Feb 6, 2019

      @zooba
      Member

      Victor may be thinking about it from time to time (or perhaps it's time to make the rest of the configuration changes plans concrete so we can all help out?), but I'd like to see this as either:

      • a helper function to fill out the core config structure from a pyvenv.cfg file (rather than hiding it deeper as it currently is), or better yet,
      • remove the dependency on all non-frozen imports at initialization and let embedders define Python code to do the initialization

      In the latter case, the main python.exe also gets to define its behavior. So for the most part, we should be able to remove getpath[p].c and move it into the site module, then make that our Python initialization step.

      This would also mean that if you are embedding Python but not allowing imports (e.g. as only a calculation engine), you don't have to do the dance of _denying_ all lookups, you simply don't initialize them.

      But as far as I know, we don't have a concrete vision for "how will consumers embed Python in their apps" that can translate into work - we're still all individually pulling in slightly different directions. Sorting that out is most important - having someone willing to do the customer engagement work to define an actual set of requirements and desirables would be fantastic.

    12. ncoghlan commented on Feb 7, 2019

      @ncoghlan
      Contributor

      Yeah, I mainly cc'ed Victor and Eric since making this easier ties into one of the original design goals for PEP-432 (even though I haven't managed to persuade either of them to become co-authors of that PEP yet).

    13. vstinner commented on Feb 13, 2019

      @vstinner
      Member

      PEP-432 will allow to give with fine control on parameters used to initialize Python. Sadly, I failed to agree with Nick Coghlan and Eric Snow on the API. The current implementation (_PyCoreConfig and _PyMainInterpreterConfig) has some flaw (don't separate clearly the early initialization and Unicode-ready state, the interpreter contains main and core config whereas some options are duplicated in both configs, etc.).

      See also bpo-35706.

    14. 57 remaining items

    15. zooba commented on Dec 20, 2024

      @zooba
      Member

      One needs to have the original (not venv) python in the PATH environment variable to find python313.dll.

      I'd strongly recommend using AddDllDirectory instead (if you're not going to redistribute the runtime). Administrators can disable using PATH for DLL resolution, not to mention anyone can hijack your DLLs, so you have an extra variable to worry about when you rely on it.

      would it be safe to set sys.executable after the initialization to the current executable?

      I thought we had a configuration field for this already? program_name is just an input to our black box of calculating paths, but I'm pretty sure I would've exposed the real thing somewhere.

      If not, then yeah, it should be safe enough to just overwrite it. However, if someone just does subprocess.run(["python", ... then Windows is going to choose a python.exe in your application directory first. So if you have (the wrong) one there, it could still be a problem. But if all your code is using sys.executable then it'll be fine - it's just a variable.

    16. ncoghlan commented on Dec 23, 2024

      @ncoghlan
      Contributor

      For the "emulating a venv" use case, I'd recommend setting config.executable (which is then used to set sys.executable) over setting config.program_name.

      Setting program_name is for the case where the given name should NOT be invoked when launching Python subprocesses (usually because the embedding application is not itself a Python interpreter).

    17. hvbtup commented on Dec 23, 2024

      @hvbtup

      Hmm, I think my last comment was misunderstood.
      First of all, while in earlier versions of my software I used the "Windows embeddable ZIP" distribution, I ditched it now. It seemed too exotic. I only used it because venv environment didn't work well on Windows at that time. I am actually using venv in a normal way.

      The only exception to this is that my executable loads and initializes a custom 3rdParty Python module which needs a secret. And my executable has a different name so that it can easily be identified in the MS Windows task manager (or in ps on Linux).

      So, I am not "emulating" a venv.
      I expect an existing Python installation including pip and venv.
      I then use the venv module to create a virtual environment in .venv, use pip inside that virtual environment to install the required 3rdParty packages.
      After configuration, to start my program, I use my custom executable instead of the python.exe from .venv\scripts.
      While python.exe finds python313.dll automagically, my program needs it on the path.

      Regarding AddDllDirectory, I tried adding a call to AddDllDirectory(...); with the path to my Python installation in my C main function before initializing python, but it doesn't even get there when that directory is not contained in PATH.

      Hmm... When venv creates the file scripts\python.exe in the .venv, does it do some DLL load magic?

    18. hvbtup commented on Dec 23, 2024

      @hvbtup

      @zooba

      Could the reason for this difference between python.exe and my own executable be related to this (from #112984 (comment))?

      The venvlauncher.c is just launcher.c boiled down to just the venv parts, and none of the py.exe parts. The only change is that the executable embeds the expected Python executable name at compile time, as we can't just assume it'll be python[_d].exe anymore.

      And it seems somewhat strange that the venvlauncher.exe (which is renamed to python.exe if I understand correctly when the venv is created) needs to start a child process. This is different from the start of python in a normal installation (not in venv). Can you explain the reasoning behind this?

    19. FFY00 commented on Dec 23, 2024

      @FFY00
      Member

      For the "emulating a venv" use case, I'd recommend setting config.executable (which is then used to set sys.executable) over setting config.program_name.

      I think I would disagree with this recommendation as executable is undocumented as a getpath input parameter. It is mostly used for internal testing. program_name, OTOH, is documented as one of the inputs. While we don't define the semantics for the Path calculation very clearly, I think it's safer to rely on that than executable. Unless there's any unwanted side effect from setting program_name, it should be preferred to use it over executable.

    20. zooba commented on Dec 24, 2024

      @zooba
      Member

      First of all, while in earlier versions of my software I used the "Windows embeddable ZIP" distribution, I ditched it now. It seemed too exotic. I only used it because venv environment didn't work well on Windows at that time. I am actually using venv in a normal way.

      You say "in my software" but also "using venv in a normal way", so I'm not clear whether you are making an application that includes a Python runtime (for which the embeddable ZIP is intended), or just trying to avoid running the installer on your own machine?

      While python.exe finds python313.dll automagically, my program needs it on the path.

      There's nothing magical - they're in the same directory, and Windows uses that as the biggest hint to find a dependency.

      Your executable either needs python313.dll in the same directory, or it needs its own launcher that doesn't directly depend on python313.dll, but is able to AddDllDirectory and then launch the executable that depends on it (the setting should be inherited). Alternatively, you could make the second executable a DLL and then LoadLibrary it, which will use the added DLL directory to resolve python313.dll and then you can call through your own interface. On second thought, this is probably the better approach.

      Ultimately, Python is designed for a Unix-style system where all references are embedded absolute paths and nothing can be relocated. On Windows, the closest way to emulate this is to include your own copy of the runtime in your application directory (and this is what the embeddable distro is for).

      It sounds like the best way to do what you seem to be doing (relying on someone else's install of Python) is to get them to provide the python.exe path, launch that with your own script (potentially in a venv created by its own venv module) and your own extension modules that know how to talk back to your application. It's a bit of a workaround, but trust me, it'll make things much easier to test and much for stable for you and your users. Trying to directly load an unknown Python runtime that someone else is managing is not a great idea - it's very easy for circumstances to break it and render your application unusable.

      And it seems somewhat strange that the venvlauncher.exe (which is renamed to python.exe if I understand correctly when the venv is created) needs to start a child process.

      Some ways to install Python don't allow directly loading its DLL, so launching a child process is the only way to do it. It would theoretically be possible to test this at runtime and only use the child process as a fallback (I have actually just been writing code that does this), but again, it's less reliable than using the proper interfaces.

      We also get to take proper advantage of Windows preferring the application directory to ensure that we load the correct dependencies, or else we may crash at runtime. Previously, we either got lucky, or we had to copy practically the entire runtime into the venv to make it work reliably. With a small, self-contained launcher, we don't have to worry about either.

      Out of interest, why do you find this strange? What were you expecting instead, and why?

    21. hvbtup commented on Dec 27, 2024

      @hvbtup

      whether you are making an application that includes a Python runtime (for which the embeddable ZIP is intended), or just trying to avoid running the installer on your own machine?

      Neither nor.

      After installing Python and creating a venv with python -m venv venv and setting the VIRTUAL_ENV environment variable to the venv directory, the only intended differences between running my executable and running the python.exe from venv\scripts is that

      • my program needs to load a 3rdParty Python module (generated with SWIG) for which a license key is needed at runtime and the executable takes care of providing the license key
      • and the executable has its own name such that it can be clearly identified in the task manager.

      So, basically, my executable is "python.exe + a properly configured 3rd party module".

      There's nothing magical - they're in the same directory, ...

      No, they are not.
      The venv\scripts directory contains python.exe, but not python313.dll.
      And I can run that python.exe directly, with a clean PATH:

      set PATH=%WINDIR%\System32;%WINDIR%;\%WINDIR%\System32\wbem
      C:\path\to\my_application\venv\Scripts>python.exe
      

      By looking into the source code of venvlauncher.c, I see that the magic happens there. As the comment at the top of the file says:

      This launcher looks for a nearby pyvenv.cfg to find the correct home directory, and then launches the original Python executable from it.

      So the reason for the creation of the python.exe child process of python.exe is exactly this DLL loading issue: To make sure that we find the python*.dll in the same directory as the executable.

      My executable code does not use this logic, which is quite complex and requires very good knowledge of the Windows process creation API.

      Duplicating this logic in my own code would be the worst solution.

      Even if that "find python home (including DLL) from a virtual environment by parsing pyvenv.cfg" would be available as a part of the initialization API, this would probably still not help in my use-case, because launching the python.exe from that python home would loose the initialization of the 3rdParty module.

      I think adding the Python home to the PATH is the most reasonable solution for my use-case.
      Note that I control the PATH environment variable and use a clean PATH when I launch the executable.
      From a security point of view, it is the same as how venv with python.exe works: An attacker would need write access to the python installation or the application installation.

      Probably my use-case is so special that I'll have to live with this little inconvenience.

      Trying to directly load an unknown Python runtime that someone else is managing is not a great idea

      I can't resist to say: This is exactly what venv is doing.

    22. zooba commented on Dec 27, 2024

      @zooba
      Member

      Trying to directly load an unknown Python runtime that someone else is managing is not a great idea

      I can't resist to say: This is exactly what venv is doing.

      We control both venv and Python. If you happen to make a venv point to a Python that we didn't provide, you'll hit issues.

      My executable code does not use this logic, which is quite complex and requires very good knowledge of the Windows process creation API.

      If it makes you feel any better, we've copy-pasted that code around more than a few times. It tries to handle many of the edge cases that people expect to Just Work, and mostly handles the most common ones (at least those presumed by POSIX developers, who expect it to work like exec).

      • my program needs to load a 3rdParty Python module (generated with SWIG) for which a license key is needed at runtime and the executable takes care of providing the license key

      I'm still intrigued by this. Not that I doubt it, but I've only come across one of these concepts before that couldn't be solved in some easier way.1 Can you provide any hint as to how this mechanism works? Or a link to docs, if it's documented somewhere? I'm interested.

      But as you say:

      Probably my use-case is so special that I'll have to live with this little inconvenience.

      Yeah, I'm afraid so. Unless there's a sudden spate of similar irresolvable requests, I'm not keen to make the venv resolution a supported API here. (Supported API meaning anyone can call it, the behaviour is consistent across all platforms, safe across most [mis]configurations, and guaranteed not to change without a proper deprecation cycle. The internal behaviour of venvlauncher and getpath are not supported APIs in this sense - only the external behaviour of launching Scripts/python.exe in an activated venv are guaranteed.)

      Footnotes

      1. That one is OpenSSL, which expects to GetProcAddress() on the running executable, which is fundamentally not a nice idea at all. But we haven't managed to get a patch contributed, and so we simply patch our own OpenSSL build to get the address from the _ssl module instead. ↩

    23. added a commit that references this issue on Jan 8, 2025
    24. BeastLe9enD commented on Aug 7, 2025

      @BeastLe9enD

      is there one example to load a virtual environment when embedding python? I created a venv and I'm trying to load it with python. I tried two things:

      1. Using Py_InitializeFromConfig and setting everything
      2. setting the venv path via PYTHONPATH env var.

      This is the script im trying to run

      from llama_index.core import SimpleDirectoryReader, VectorStoreIndex, Settings
      from llama_index.llms.ollama import Ollama
      
      class AiContext:
          def __init__(self, model: str, url: str, data_directory: str):
              llm = Ollama(model = model, base_url = url)
              Settings.llm = llm
      
              documents = SimpleDirectoryReader(data_directory).load_data()
              index = VectorStoreIndex.from_documents(documents)
              self.query_engine = index.as_query_engine()
      
          def run_prompt(self, prompt: str) -> str:
              response = self.query_engine.query(prompt)
              return str(response)

      without my venv set attempts, llama_index is obviously not found since its only present inside the venv. With 1 or 2, it finds llama_index, but I cannot use SimpleDirectoryReader nor Ollama (no types that come from the packages inside the venv). When I run the same script inside the same venv, running activate and then just python my_script.py, it works fine.

      I'm honestly quite confused, as there are no instructions on how to handle virtual environments and Python embedding. Is this a bug in Python, or is my way of setting up venv simply wrong? If so, is there a reference example that shows exactly what the right way is?

    25. hvbtup commented on Aug 11, 2025

      @hvbtup

      Not a solution actually, but maybe this helps others:
      I gave up using the "Embeddable ZIP" distribution and switched to using venv with a normal Python, but instead of python.exe I use a hand-crafted main program (also because I need a Windows and a Linux version, and the "Embeddable ZIP" is only available on Windows.

      I wanted a customized Windows executable with an icon, version information etc.

      This was much easier, though there was still one quirk to work around:
      It needs an environment variable VIRTUAL_ENV to find the python.exe in the venv.

      In my case I'm using a C library for PDF handling, for which I generated a Python wrapper with SWIG.

      Note: This is for Windows only. For Linux, some modifications are necessary.

      My executable behaves like python.exe, because it calls Py_RunMain, but I think this could be changed quite easily.

      #include <windows.h>
      #include "Python.h"
      #include <string.h>
      #include <stdlib.h>
      #include <stdio.h>
      #include ...
      
      #define PATH_MAX 200
      
      PyObject* PyInit__pdf(void);
      
      /* We need this to workaround a quirk when using venv 
         in combination with anything else but a (possibly renamed) python.exe
      */
      void get_interpreter_path(const char* virtual_env, wchar_t* interpreter_path) {
      	char python_exe[PATH_MAX];
      	sprintf(python_exe, "%s\\scripts\\python.exe", virtual_env);
      	wchar_t* temp = Py_DecodeLocale(python_exe, NULL);
      	wcsncpy(interpreter_path, temp, PATH_MAX);
      	if (interpreter_path[PATH_MAX - 1] != 0) {
      		printf("Interpreter path too long!");
      		exit(1);
      	}
      }
      
      void init_python( int argc, char* argv[], PyStatus* status, PyPreConfig* preconfig, PyConfig* config ) {
      
      	int rc;
      	static wchar_t interpreter_path[PATH_MAX];
      
      	PyPreConfig_InitPythonConfig(preconfig);
      
      	preconfig->utf8_mode = 1;
      	preconfig->allocator = PYMEM_ALLOCATOR_MALLOC;
      
          *status = Py_PreInitialize(preconfig);
          if (PyStatus_Exception(*status)) {
              goto pre_exception;
          }
      
      	PyConfig_InitPythonConfig(config);
      
      	PyConfig_SetBytesArgv(config, argc, argv);
          if (PyStatus_Exception(*status)) {
              goto init_exception;
          }
      
      	const char* virtual_env = getenv("VIRTUAL_ENV");
      
      	if (virtual_env != NULL) {
      		get_interpreter_path(virtual_env, interpreter_path);		
      		PyConfig_SetString(config, &(config->program_name), interpreter_path);
      	}
      
      	// provide the PDF library as a Python module
      	rc = PyImport_AppendInittab("...", PyInit__pdf);
      	if (rc != 0) {
      		printf("Returncode from PyImport_AppendInittab: %d\n", rc);
      		PyStatus_Error("Unable to initialize PDF library");
      		goto pre_exception;
      	}
      
          *status = Py_InitializeFromConfig(config);
          if (PyStatus_Exception(*status)) {
              goto init_exception;
          }
      
          PyConfig_Clear(config);
          return;
      
      init_exception:
      	PyConfig_Clear(config);
      pre_exception:
          Py_ExitStatusException(*status);
      }
      
      int main( int argc, char* argv[] ) {
      	int rc = 0;
      
      	PyStatus status;
      	PyPreConfig preconfig;
      	PyConfig    config;
      
      	/* Initialize the PDF library at the C level. */
      	...
      
      	/* Start Python normally and provide the PDF library to Python */
      	init_python(argc, argv, &status, &preconfig, &config);
      
      	rc = Py_RunMain();
      
      	/* Shutdown the PDF library */
      	...
      
          PyConfig_Clear(&config);
          if (PyStatus_Exception(status)) {
      		/* Display the error message and exit the process with
      		non-zero exit code */
      		Py_ExitStatusException(status);	
      	} else if (PyStatus_IsExit(status)) {
              return status.exitcode;
      	}
      	return rc;
      }
    26. zooba commented on Aug 11, 2025

      @zooba
      Member

      Not a solution actually, but maybe this helps others

      This is our preferred solution, and your users will have a much better time when you embed Python properly (like this) rather than trying to hack around the interactive REPL (like everyone else wants to).

    27. flameastro commented on Jul 11, 2026

      @flameastro
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      docsDocumentation in the Doc dirtestsTests in the Lib/test dirtopic-venvRelated to the venv moduletype-featureA feature request or enhancement

      Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions