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

venv python reports wrong sys.executable in a subprocess on Windows #83086

Open
uranusjr mannequin opened this issue Nov 24, 2019 · 33 comments
Open

venv python reports wrong sys.executable in a subprocess on Windows #83086

uranusjr mannequin opened this issue Nov 24, 2019 · 33 comments
Labels
3.10 only security fixes OS-windows

Comments

@uranusjr
Copy link
Mannequin

uranusjr mannequin commented Nov 24, 2019

BPO 38905
Nosy @pfmoore, @ericvsmith, @tjguk, @zware, @eryksun, @zooba, @uranusjr, @nsmcan, @nirvana-msu

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 2019-11-24.08:17:40.212>
labels = ['3.10', 'OS-windows']
title = 'venv python reports wrong sys.executable in a subprocess on Windows'
updated_at = <Date 2021-03-29.22:53:56.136>
user = 'https://github.com/uranusjr'

bugs.python.org fields:

activity = <Date 2021-03-29.22:53:56.136>
actor = 'steve.dower'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Windows']
creation = <Date 2019-11-24.08:17:40.212>
creator = 'uranusjr'
dependencies = []
files = []
hgrepos = []
issue_num = 38905
keywords = []
message_count = 30.0
messages = ['357392', '357393', '357394', '357421', '357425', '357428', '357429', '357430', '357437', '357439', '357440', '357453', '357456', '357457', '357459', '357460', '357479', '357519', '366755', '366824', '370656', '371357', '378284', '385923', '389321', '389345', '389741', '389760', '389761', '389768']
nosy_count = 12.0
nosy_names = ['paul.moore', 'eric.smith', 'tim.golden', 'Jurko.Gospodneti\xc4\x87', 'zach.ware', 'eryksun', 'steve.dower', 'uranusjr', 'Benedek R\xc3\xa1cz', 'nsmcan', 'nirvana-msu', 'awaizman']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = None
url = 'https://bugs.python.org/issue38905'
versions = ['Python 3.10']

@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 24, 2019

To reproduce:

> py -m venv fooenv
> fooenv\Scripts\activate.bat
(fooenv) > python -c "import sys; print(sys.executable)"  % This is correct
C:\Users\uranusjr\Downloads\venvtest\Scripts\python.exe
(fooenv) > python -q
>>> import subprocess
>>> subprocess.check_output(['python', '-c', 'import sys; print(sys.executable)'])
b'C:\\Users\\uranusjr\\AppData\\Local\\Programs\\Python\\Python37\\python.exe\r\n'

The output shows the base interpreter, not the interpreter in venv. Not sure whether this is a venv or subprocess problem.

@uranusjr uranusjr mannequin added stdlib Python modules in the Lib dir 3.7 (EOL) end of life 3.8 (EOL) end of life OS-windows labels Nov 24, 2019
@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 24, 2019

Linux works correctly (Ubuntu with self-compiled Python 3.7.5)

$ python3.7 -m venv fooenv
$ . fooenv/bin/activate
(fooenv) $ python -c "import sys; print(sys.executable)"
/home/uranusjr/fooenv/bin/python
(fooenv) $ python -q
>>> import subprocess
>>> subprocess.check_output(['python', '-c', 'import sys; print(sys.executable)'])
b'/home/uranusjr/fooenv/bin/python\n'

@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 24, 2019

3.6 works correctly on Windows:

> py -3.6 -m venv test36
> test36\Scripts\activate.bat
>>> import subprocess
>>> print(subprocess.check_output(['python', '-c', 'import sys; print(sys.executable)']))
b'C:\\Users\\uranusjr\\Downloads\\test36\\Scripts\\python.exe\r\n'

So it seems the problem is introduced sometime after.

@ericvsmith
Copy link
Member

Your failing test case with 3.7 works for me.

If you don't use activate.bat, but just run the venv's python directly, what do you see? I get:

py -m venv fooenv

fooenv\Scripts\python -V
Python 3.7.0

>fooenv\Scripts\python -q
>>> import subprocess
>>> subprocess.check_output(['python', '-c', 'import sys; print(sys.executable)'])
b'C:\\Users\\XXXXXXX\\fooenv\\Scripts\\python.exe\r\n'

What shell are you using? Above is with cmd.exe.

If you "echo %PATH%" after activate.bat, what do you see?

Before running activate.bat, do you have a python.exe in your path? If so, is it the one that subprocess is reporting?

@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 25, 2019

If you don't use activate.bat, but just run the venv's python directly, what do you see? I get:
What shell are you using? Above is with cmd.exe.

I get the same result as activating (i.e. shows the base interpeter). All results in cmd.exe as well.

If you "echo %PATH%" after activate.bat, what do you see?
Before running activate.bat, do you have a python.exe in your path? If so, is it the one that subprocess is reporting?

PATH is as expected, the venv’s Scripts directory at the front after activation. I (only) have a python.exe from Windows Store in PATH. The one reported by subprocess is not in PATH.

I’ll try to find a clean machine (maybe a VM) and try whether I can replicate this. BTW the problematic versions for me was 3.7.5 and 3.8.0.

@pfmoore
Copy link
Member

pfmoore commented Nov 25, 2019

The behaviour in this area is different between 3.7.0, 3.7.2, and 3.7.3 (at least). I have reproduced the issue with 3.7.3. Steve Dower made changes to the way the python executable works in venvs in the point releases of 3.7 - see pypa/virtualenv#1380 and pypa/virtualenv#1339 for some discussion of how this affected virtualenv.

I suspect this issue is related - from 3.7.2 onwards, the python.exe in a venv is a redirector which runs the "base" python.exe, but with sys.executable showing the redirector rather than the actual running exe.

@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 25, 2019

I tested the following in various versions (all 64-bit) in a VM. All installations are 64-bit per-user.

py -m venv testenv
testenv\Scripts\python.exe -c "import subprocess; print(subprocess.check_output(['python', '-c', 'import sys; print(sys.executable)']))"

3.8.0: Incorrect
3.7.5: Incorrect
3.7.4: Incorrect
3.7.3: Incorrect
3.7.2: Correct
3.6.8: Correct
3.7.1: Correct
3.7.0: Correct

So the change seems to have happened somewhere between 3.7.2 and 3.7.3. Does this timeline line up with the venv redirector change?

@pfmoore
Copy link
Member

pfmoore commented Nov 25, 2019

Yes, it does.

I think we'd need input from Steve Dower here, as these changes were made (I believe) in support of the Windows Store build of Python, so any changes would need to be considered in the light of how they would affect that. I do, however, consider this to be a regression that should be fixed.

BTW, just for completeness,

>> subprocess.check_output([sys.executable, '-c', 'import sys; print(sys.executable)'])

works as I'd expect, and that's the idiom that is often used. So relying on a path search to find the correct Python can be considered an unusual case (but nevertheless one I'd expect to be fixed).

I assume that the issue here is that the code is being run by the python.dll in the base environment, and whens searching for executables, Windows gives "exes that are in the same directory as the currently executing code" priority over PATH. See https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw, specifically

"""
If the file name does not contain a directory path, the system searches for the executable file in the following sequence:

  1. The directory from which the application loaded.
  2. The current directory for the parent process.
  3. The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
  4. The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched. The name of this directory is System.
  5. The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
  6. The directories that are listed in the PATH environment variable. Note that this function does not search the per-application path specified by the App Paths registry key. To include this per-application path in the search sequence, use the ShellExecute function.
    """

@eryksun
Copy link
Contributor

eryksun commented Nov 25, 2019

whens searching for executables, Windows gives "exes that are in the
same directory as the currently executing code" priority over PATH.

That subprocess lets CreateProcessW use a platform-dependent search that prioritizes the application directory has come up in previous issues. To avoid this, we'd have to implement our own search for the given or parsed executable name. Then pass the fully-qualified executable path as the lpApplicationName name of CreateProcessW. This is how CMD works, since it has its own search routine that incorporates the PATHEXT environment variable.

Because the application directory is searched before the working directory (if the working directory is searched at all, depending on context), this issue also affects searching for executable paths that contain a path separator. In Unix a relative path that contains a path separator is always relative to the working directory, but Windows CreateProcessW uses a normal search for a relative name unless it explicitly references the working directory as "." (e.g. ".\Scripts\pip.exe" instead of "Scripts\pip.exe").

@pfmoore
Copy link
Member

pfmoore commented Nov 25, 2019

I presume there's also the option of setting up the environment (or however it's done now - I know the details changed as the feature was developed) so that the "base" python.exe pretends to be the venv one, exactly as the wrapper does.

However, that may well have other difficult-to-fix implications, not least that calling the base Python using an explicit full path should act like the base Python, and *not* like the venv one.

IMO, the key thing here is that either the various limitations/quirks of redirecting to the base Python should either be treated as bugs, or they should be documented (even if only in the form of explicitly saying not to rely on any specific behaviour - e.g. "running an unqualified python and expecting a PATH search to pick up the same executable as the parent shell would is not supported and may produce unexpected results").

Virtual environments are a key part of most Python development workflows, and virtualenv is in the process of switching to use the core venv module internally. When that happens, there will be a lot more visibility for unexpected behaviours like this one.

@eryksun
Copy link
Contributor

eryksun commented Nov 25, 2019

"running an unqualified python and expecting a PATH search to pick up
the same executable as the parent shell would is not supported and may
produce unexpected results"

CreateProcessW finds "python.exe" in __APPDIR__ before it even searches PATH. I expect that some scripts depend on this when python.exe isn't in PATH, or when a different version is in PATH. If subprocess implements its own search, it can continue to prioritize the *effective* application directory, from dirname(sys.executable).

@zooba
Copy link
Member

zooba commented Nov 25, 2019

Yeah, this definitely relates to how Windows handles unqualified argv[0] in CreateProcess. Though I thought we checked that out in another issue and decided that "most" people are correctly using sys.executable here? (Or decided that it was documented well enough that they should, and using a "python" literal was relying on OS behaviour.)

I'm not a fan of trying to override the OS handling in subprocess, though that would be the fix. Possibly we could handle "python[.exe]" literals by substituting sys.executable transparently?

@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 25, 2019

To provide concrete context, the problem I’m facing is with how Flit resolves flit install --python:

https://github.com/takluyver/flit/blob/7e65ffc7a540d76b96de0df473d3edff6f97c26c/flit/__init__.py#L18-L28

Generally the setup is to install Flit into a globally available location (let’s name it env A), so it’s usable for every project and environment. For a project foo you’d have a virtual environment (env X) that’s created from a base interpreter (env B, which may or may not be the same as env A).

So the comment workflow would look like this:

pythonB -m venv project-env
project-env\Scripts\activate.bat
(project-env) > pythonA -m flit install --python=pythonX

This results in the following subprocess call:

subprocess.check_output(
["pythonX", "-c", "import sys; print(sys.executable)"],
universal_newlines=True,
).strip()

And ideally (pre-3.7.2 Windows, or current POSIX behaviour) this would give you the absolute path to pythonX. But right now on Windows the result is pythonB.

So if this is to be determined as acceptable behaviour, we’d need to come up with a suggestion how this code can be rewritten. shutil.which could be a direction, but still not enough since it’d break flit install --python=py because that’s give you the location of py.exe, not the actual interperter. What else?

@zooba
Copy link
Member

zooba commented Nov 25, 2019

shutil.which could be a direction, but still not enough since it’d break flit install --python=py because that’s give you the location of py.exe, not the actual interperter.

This would be fine if you still run the process to get its sys.executable.

Your specific example would never have worked, FWIW, as it always would have picked up pythonA rather than the application one or the base one, unless you were relying on python3/python3.7 not being available on Windows (which is no longer true - they are included in the Store package now).

@eryksun
Copy link
Contributor

eryksun commented Nov 25, 2019

Possibly we could handle "python[.exe]" literals by substituting
sys.executable transparently?

Perhaps generalize this as splitext(basename(sys.executable))[0] in order to support names other than "python" and "pythonw". It would have to handle quoting enough to ignore an initial double quote when parsing the executable name out of an args string, or _winapi could wrap the shell's CommandLineToArgvW function to handle this.

@zooba
Copy link
Member

zooba commented Nov 25, 2019

Perhaps generalize this as splitext(basename(sys.executable))[0] in
order to support names other than "python" and "pythonw". It would have
to handle quoting enough to ignore an initial double quote when parsing
the executable name out of an args string, or _winapi could wrap the
shell's CommandLineToArgvW function to handle this.

This is where we've hit the point of complexity that it would have to be added as a new API. And since it's now opt-in, we may as well document that shutil.which() is the recommended way to resolve a filename against PATH (and make that work properly... ISTR it has some inconsistencies with the OS).

@uranusjr
Copy link
Mannequin Author

uranusjr mannequin commented Nov 26, 2019

not enough since it’d break flit install --python=py because that’s give you the location of py.exe, not the actual interperter.
This would be fine if you still run the process to get its sys.executable.

But then I need two separate workflows based on what is passed in. For py.exe I need to run it and get sys.executable. But for python.exe I cannot use sys.executable because that’s the base interepeter, not the venv path I want. And if Path(arg).stem == "py" just seems like a bug waiting to happen.

Your specific example would never have worked, FWIW, as it always would have picked up pythonA rather than the application one or the base one, unless you were relying on python3/python3.7 not being available on Windows (which is no longer true - they are included in the Store package now).

It is an illustration. I am fully aware of Windows not having version-named python executables. Thanks for the reminder anyway.

@zooba
Copy link
Member

zooba commented Nov 26, 2019

But then I need two separate workflows based on what is passed in. For py.exe I need to run it and get sys.executable. But for python.exe I cannot use sys.executable because that’s the base interepeter, not the venv path I want. And if Path(arg).stem == "py" just seems like a bug waiting to happen.

If you use shutil.which() to resolve "python" or "py" against PATH (which doesn't include the application directory), then you'll get the full path to the correct python.exe and will get the expected sys.executable. So it's only one flow that works for both once you add the shutil.which step.

@BenedekRcz
Copy link
Mannequin

BenedekRcz mannequin commented Apr 19, 2020

Is there any update/solution for this issue? This issue is the root cause of this SO post: https://stackoverflow.com/q/61290972/2506522

@zooba
Copy link
Member

zooba commented Apr 20, 2020

I posted a workaround right above your post for when you want to resolve executables against PATH instead of using Windows's normal rules.

I'm not sure we reached any good approach for launching sys.executable in a venv and automatically bypassing the redirector.

@JurkoGospodneti
Copy link
Mannequin

JurkoGospodneti mannequin commented Jun 3, 2020

encountered what I believe is the same problem, so here are some more details I noticed, after checking what exactly that process name lookup does using ProcessMonitor:

  • when running my external process in a venv generated virtual environment, POpen() started subprocess executables are first looked up in the base Python executable's folder (the one used to create our virtual environment) as opposed to the one running the external Python process, if POpen() is called with shell=False

  • when doing the same and passing shell=True to the POpen() call, there is no such initial folder in the lookup

  • when doing the same on a virtualenv generated virtual environment, the file lookup is done in the correct folder first - the one containing the current Python executable and not in the base one

@zooba
Copy link
Member

zooba commented Jun 12, 2020

Thanks for confirming that reality aligns with the documentation.

when doing the same and passing shell=True to the POpen() call, there is no such initial folder in the lookup

Correct, because a fully qualified path to cmd.exe is used, and cmd.exe's application folder is not Python's.

Ultimately, the above behaviour could have occurred anyway, so all that has really happened is that it is now more obvious. This is what always happens when you include support for other platforms - you find out which of your previous assumptions were incorrect (same principle applies to all of life if you think about it).

If someone wants to update subprocess to manually search PATH when just provided with a filename, I'm okay with that. It would be a new enhancement though.

For earlier versions, updating the docs to clarify that subprocess does not do a PATH search but relies on operating system behaviour for relative paths is probably the most helpful way to raise awareness.

@zooba zooba added 3.10 only security fixes and removed stdlib Python modules in the Lib dir 3.7 (EOL) end of life 3.8 (EOL) end of life labels Jun 12, 2020
@nsmcan
Copy link
Mannequin

nsmcan mannequin commented Oct 8, 2020

Hello,

I've just run into a related issue. I have a python script, which starts an another python script using subprocess.Popen(). The parent script gets pid of the child and monitors up its activity through a database by this pid. The child script updates its activity in the database using the pid it gotten from os.getpid()

Both scripts live in a virtual environment.

It worked fine in Python 3.5 and stopped working after migration to Python 3.8.

My base Python location: D:\Python\Python38\pythonw.exe
Virtual Environment: D:\test\venv\Scripts\pythonw.exe

I have realized, that when I run the following from a command prompt:
D:\test\venv\Scripts\pythonw.exe test.py

2 processes with the different PIDs are created:
PID: 97040
Parent PID: 12004 (cmd.exe)
Command Line: D:\test\venv\Scripts\pythonw.exe test.py

PID: 85548
Parent PID: 97040 (pythonw.exe)
Command Line: D:\Python\Python38\pythonw.exe test.py

It is definitely a regression, and will potentially break a lot of applications expecting a child Python process to be a direct descendant of its parent.

Also it is a waste of system resources

@nirvana-msu
Copy link
Mannequin

nirvana-msu mannequin commented Jan 29, 2021

Another victim of this change in venv behavior is Ray, which hangs forever because the workers fail to register as parent does not recognize their PIDs.
ray-project/ray#13794

@awaizman
Copy link
Mannequin

awaizman mannequin commented Mar 22, 2021

The fact that now there is a redirector process seems to me like a regression issue.
I have an application which Popen several processes and uses the Popen object in order to get the subprocesses pids and act upon this pids (i.e. looks for log files with associated pid).

Now this breaks because the subprocess pid is the one of the redirector and not the actual one.

This is similar to 'Ray' issue mentioned earlier.

@eryksun
Copy link
Contributor

eryksun commented Mar 22, 2021

In some cases, the problem can be worked around by setting the __PYVENV_LAUNCHER__ environment variable and executing the base executable. For example:

    import os
    import sys
    import subprocess

    def get_python_exe_env():
        if sys.executable == getattr(sys, '_base_executable', sys.executable):
            return sys.executable, None
        environ = os.environ.copy()
        environ['__PYVENV_LAUNCHER__'] = sys.executable
        return sys._base_executable, environ
executable, environ = get_python_exe_env()
p = subprocess.Popen([executable], env=environ)

---

As is, I don't know how to solve the problem in which an arbitrary application runs a script using the "python" launcher. Half of the problem could be solved. When the launcher creates the base Python process, it could set the parent via PROC_THREAD_ATTRIBUTE_PARENT_PROCESS [1], presuming it can open a handle for the parent application with PROCESS_CREATE_PROCESS access. However, this doesn't solve the problem from the application's perspective. It still gets the handle and process ID of the launcher.

If the purpose of using a launcher was only to allow an in-place upgrade of the base "python3x.dll", then an alternative design would be to set the __PYVENV_LAUNCHER__ environment variable, load the Python DLL from the pyvenv.cfg "home" directory, and call Py_Main(argc, argv). This would eliminate the parent<->child problem and the application directory (APPDIR) problem.

However, naively implementing something like that cannot work for the store app distribution. "python3x.dll" in the app installation directory under "%ProgramFiles%\WindowsApps" only grants execute access to users that have the app's WIN://SYSAPPID identifier in their access token, which gets added by CreateProcessW() when the "python[3[.x]].exe" appexec link is executed. I'd have to experiment to see what works. Maybe "python.exe" in the virtual environment could be created as an appexec link to "venvlauncher.exe" in the app, which loads the DLL, etc.

---

[1] https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute

@zooba
Copy link
Member

zooba commented Mar 29, 2021

The main motivator for the redirector was to support the Store package, which can only be *executed* from outside its container (we can't load the DLL directly). Given we want the two distributions to be interchangeable from a code POV, it made the most sense to switch both over. That said, there were other issues with the previous system that were worth fixing, it just happens that a new one was introduced (though it definitely already existed for some users - IPC is a complex pattern to get right even when you fully control the processes).

I still haven't been able to come up with a viable workaround that doesn't break more cases than it helps. Passing the internal environment variable around might help, but I don't want to fully support it because then that would prevent us fixing this properly!

PEP-582 (or otherwise getting away from virtual environments in favour of some other way of having project-specific package installation) may be our best hope.

@eryksun
Copy link
Contributor

eryksun commented Mar 29, 2021

the Store package, which can only be *executed* from outside its
container (we can't load the DLL directly).

The idea I was pondering was to create "python.exe" in the virtual environment as an appexec link to "C:\Program Files\WindowsApps\venvlauncher.exe" and reverse engineer whatever is needed to make CreateProcessW() build the required access token. For example, if it simply checks for the existence of "venvlauncher.exe" in the user's "WindowsApps" directory, then set that as an appexec alias as well.

However, it can't work for a simple reason. For an appexec link, CreateProcessW() reads and executes the target of the link. The original appexec link path is not retained, so there's no way for the "venvlauncher.exe" process to know about the virtual environment.

PEP-582

"__pypackages__" doesn't seem like a drop-in replacement for virtual environments. The current directory (in the REPL) and script directory have always had precedence over the standard library, except in isolated mode, so "__pypackages__" seems more about providing a directory for Python packages that's neatly separated from the script directory. I presume also that "__pypackages__" would be added to sys.path even in isolated mode, though the PEP makes no mention of it.

@eryksun
Copy link
Contributor

eryksun commented Mar 29, 2021

"C:\Program Files\WindowsApps\venvlauncher.exe"

The above was supposed to be "C:\Program Files\WindowsApps\Python...\venvlauncher.exe", where "Python..." is the elided name of the package directory.

@zooba
Copy link
Member

zooba commented Mar 29, 2021

"__pypackages__" doesn't seem like a drop-in replacement for virtual environments.
Right, it's not. But if enough people adapt their workflows to use
[something like] it rather than relying on having a local copy of
python.exe in order to launch, apps that currently assume that their
subprocesses don't have their own children would be correct again.

If it was a drop-in replacement, we'd have dropped it in already. All
the drop in replacements come with similar issues, which is why we need
to change things more broadly in order to handle all the use cases that
exist.

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
@mattip
Copy link
Contributor

mattip commented May 18, 2022

It seems to me sys.executable and os.getpid() should refer to the same process. Once the launcher is involved they do not. Is the internal inconsistency part of this issue or a different one?

This is from a PR to ray:

>>> import sys, os, psutil
>>> >>> print(sys.executable)
C:\temp\issue24361\Scripts\python.exe   # the launcher
>>> os.getpid()
2208
>>> child = psutil.Process(2208)
>>> child.cmdline()
['C:\\oss\\CPython38\\python.exe']     # the actual Python
>>> child.parent().cmdline()
['C:\\temp\\issue24361\\Scripts\\python.exe']
>>> child.parent().pid
6424                                   # this is the PID of the launcher

@zooba
Copy link
Member

zooba commented May 18, 2022

Unfortunately, getting the PID from the process directly rather than through a backchannel is simply not compatible with the other scenarios that venv has to support. We need the redirector process, which means you can't launch it and get the actual PID directly (this is one of the issues I'd hoped to solve by moving to a PEP 582-style world).

Simple waits against the redirector will work fine, but for anything else you need the child process to print out its PID for the host, or use some other mechanism to communicate (named pipes, shared memory, etc.).

@Stefanhg
Copy link

Is there any updates on this @zooba?
Now that PEP-582 has been rejected, this is not resolved automatically.
I ran into this issue in raczben/wexpect#58 which frustrates me as it is until now the only package I've found for Windows that works exactly for what i need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.10 only security fixes OS-windows
Projects
None yet
Development

No branches or pull requests

6 participants