8
8
from pathlib import Path
9
9
from typing import BinaryIO , TextIO , cast
10
10
11
+ import anyio
11
12
from anyio import to_thread
13
+ from anyio .abc import Process
12
14
from anyio .streams .file import FileReadStream , FileWriteStream
13
15
14
16
@@ -113,13 +115,14 @@ async def create_windows_process(
113
115
env : dict [str , str ] | None = None ,
114
116
errlog : TextIO | None = sys .stderr ,
115
117
cwd : Path | str | None = None ,
116
- ) -> FallbackProcess :
118
+ ) -> Process | FallbackProcess :
117
119
"""
118
120
Creates a subprocess in a Windows-compatible way.
119
121
120
- On Windows, asyncio.create_subprocess_exec has incomplete support
121
- (NotImplementedError when trying to open subprocesses).
122
- Therefore, we fallback to subprocess.Popen and wrap it for async usage.
122
+ Attempt to use anyio's open_process for async subprocess creation.
123
+ In some cases this will throw NotImplementedError on Windows, e.g.
124
+ when using the SelectorEventLoop which does not support async subprocesses.
125
+ In that case, we fall back to using subprocess.Popen.
123
126
124
127
Args:
125
128
command (str): The executable to run
@@ -131,6 +134,45 @@ async def create_windows_process(
131
134
Returns:
132
135
FallbackProcess: Async-compatible subprocess with stdin and stdout streams
133
136
"""
137
+ try :
138
+ # First try using anyio with Windows-specific flags to hide console window
139
+ process = await anyio .open_process (
140
+ [command , * args ],
141
+ env = env ,
142
+ # Ensure we don't create console windows for each process
143
+ creationflags = subprocess .CREATE_NO_WINDOW # type: ignore
144
+ if hasattr (subprocess , "CREATE_NO_WINDOW" )
145
+ else 0 ,
146
+ stderr = errlog ,
147
+ cwd = cwd ,
148
+ )
149
+ return process
150
+ except NotImplementedError :
151
+ # Windows often doesn't support async subprocess creation, use fallback
152
+ return await _create_windows_fallback_process (command , args , env , errlog , cwd )
153
+ except Exception :
154
+ # Try again without creation flags
155
+ process = await anyio .open_process (
156
+ [command , * args ],
157
+ env = env ,
158
+ stderr = errlog ,
159
+ cwd = cwd ,
160
+ )
161
+ return process
162
+
163
+
164
+ async def _create_windows_fallback_process (
165
+ command : str ,
166
+ args : list [str ],
167
+ env : dict [str , str ] | None = None ,
168
+ errlog : TextIO | None = sys .stderr ,
169
+ cwd : Path | str | None = None ,
170
+ ) -> FallbackProcess :
171
+ """
172
+ Create a subprocess using subprocess.Popen as a fallback when anyio fails.
173
+
174
+ This function wraps the sync subprocess.Popen in an async-compatible interface.
175
+ """
134
176
try :
135
177
# Try launching with creationflags to avoid opening a new console window
136
178
popen_obj = subprocess .Popen (
0 commit comments