@@ -52,7 +52,16 @@ export async function setupPipPackWithPython(
52
52
const isPipx = usePipx && ! isLibrary && ( await hasPipx ( givenPython ) )
53
53
const pip = isPipx ? "pipx" : "pip"
54
54
55
- const hasPackage = await pipHasPackage ( givenPython , name )
55
+ // if upgrade is not requested, check if the package is already installed, and return if it is
56
+ if ( ! upgrade ) {
57
+ const installed = await pipPackageIsInstalled ( givenPython , pip , name )
58
+ if ( installed ) {
59
+ const binDir = await finishPipPackageInstall ( givenPython , name )
60
+ return { binDir }
61
+ }
62
+ }
63
+
64
+ const hasPackage = await pipHasPackage ( givenPython , pip , name )
56
65
if ( hasPackage ) {
57
66
try {
58
67
info ( `Installing ${ name } ${ version ?? "" } via ${ pip } ` )
@@ -79,18 +88,19 @@ export async function setupPipPackWithPython(
79
88
throw new Error ( `Failed to install ${ name } via ${ pip } : ${ err } .` )
80
89
}
81
90
}
82
- } else {
83
- if ( ( await setupPipPackSystem ( name ) ) === null ) {
84
- throw new Error ( `Failed to install ${ name } as it was not found via ${ pip } or the system package manager` )
85
- }
91
+ } else if ( ( await setupPipPackSystem ( name ) ) === null ) {
92
+ throw new Error ( `Failed to install ${ name } as it was not found via ${ pip } or the system package manager` )
86
93
}
87
94
88
- const execPaths = await addPythonBaseExecPrefix ( givenPython )
89
- const binDir = await findBinDir ( execPaths , name )
95
+ const binDir = await finishPipPackageInstall ( givenPython , name )
96
+ return { binDir }
97
+ }
90
98
99
+ async function finishPipPackageInstall ( givenPython : string , name : string ) {
100
+ const pythonBaseExecPrefix = await addPythonBaseExecPrefix ( givenPython )
101
+ const binDir = await findBinDir ( pythonBaseExecPrefix , name )
91
102
await addPath ( binDir , rcOptions )
92
-
93
- return { binDir }
103
+ return binDir
94
104
}
95
105
96
106
export async function hasPipx ( givenPython : string ) {
@@ -153,8 +163,54 @@ async function getPython_(): Promise<string> {
153
163
}
154
164
const getPython = memoize ( getPython_ , { promise : true } )
155
165
156
- async function pipHasPackage ( python : string , name : string ) {
157
- const result = await execa ( python , [ "-m" , "pip" , "-qq" , "index" , "versions" , name ] , {
166
+ type PipxShowType = {
167
+ venvs : Record < string , {
168
+ metadata : {
169
+ main_package : {
170
+ package : string
171
+ package_or_url : string
172
+ apps : string [ ]
173
+ }
174
+ }
175
+ } >
176
+ }
177
+
178
+ async function pipPackageIsInstalled ( python : string , pip : string , name : string ) {
179
+ try {
180
+ if ( pip === "pipx" ) {
181
+ const result = await execa ( python , [ "-m" , pip , "list" , "--json" ] , {
182
+ stdio : "ignore" ,
183
+ reject : false ,
184
+ } )
185
+ if ( result . exitCode !== 0 || typeof result . stdout !== "string" ) {
186
+ return false
187
+ }
188
+
189
+ const pipxOut = JSON . parse ( result . stdout as unknown as string ) as PipxShowType
190
+ // search among the venvs
191
+ if ( name in pipxOut . venvs ) {
192
+ return true
193
+ }
194
+ // search among the urls
195
+ for ( const venv of Object . values ( pipxOut . venvs ) ) {
196
+ if ( venv . metadata . main_package . package_or_url === name || venv . metadata . main_package . package === name ) {
197
+ return true
198
+ }
199
+ }
200
+ }
201
+
202
+ const result = await execa ( python , [ "-m" , pip , "-qq" , "show" , name ] , {
203
+ stdio : "ignore" ,
204
+ reject : false ,
205
+ } )
206
+ return result . exitCode === 0
207
+ } catch {
208
+ return false
209
+ }
210
+ }
211
+
212
+ async function pipHasPackage ( python : string , pip : string , name : string ) {
213
+ const result = await execa ( python , [ "-m" , pip , "-qq" , "index" , "versions" , name ] , {
158
214
stdio : "ignore" ,
159
215
reject : false ,
160
216
} )
0 commit comments