1
1
import json
2
2
import logging
3
3
from optparse import Values
4
- from typing import Iterator , List , Set , Tuple
4
+ from typing import TYPE_CHECKING , Iterator , List , Optional , Sequence , Tuple , cast
5
5
6
- from pip ._vendor .pkg_resources import Distribution
6
+ from pip ._vendor .packaging . utils import canonicalize_name
7
7
8
8
from pip ._internal .cli import cmdoptions
9
9
from pip ._internal .cli .req_command import IndexGroupCommand
10
10
from pip ._internal .cli .status_codes import SUCCESS
11
11
from pip ._internal .exceptions import CommandError
12
12
from pip ._internal .index .collector import LinkCollector
13
13
from pip ._internal .index .package_finder import PackageFinder
14
+ from pip ._internal .metadata import BaseDistribution , get_environment
14
15
from pip ._internal .models .selection_prefs import SelectionPreferences
15
16
from pip ._internal .network .session import PipSession
16
- from pip ._internal .utils .compat import stdlib_pkgs
17
- from pip ._internal .utils .misc import (
18
- dist_is_editable ,
19
- get_installed_distributions ,
20
- tabulate ,
21
- write_output ,
22
- )
23
- from pip ._internal .utils .packaging import get_installer
17
+ from pip ._internal .utils .misc import stdlib_pkgs , tabulate , write_output
24
18
from pip ._internal .utils .parallel import map_multithread
25
19
20
+ if TYPE_CHECKING :
21
+ from pip ._internal .metadata .base import DistributionVersion
22
+
23
+ class _DistWithLatestInfo (BaseDistribution ):
24
+ """Give the distribution object a couple of extra fields.
25
+
26
+ These will be populated during ``get_outdated()``. This is dirty but
27
+ makes the rest of the code much cleaner.
28
+ """
29
+ latest_version : DistributionVersion
30
+ latest_filetype : str
31
+
32
+ _ProcessedDists = Sequence [_DistWithLatestInfo ]
33
+
34
+
26
35
logger = logging .getLogger (__name__ )
27
36
28
37
@@ -145,14 +154,16 @@ def run(self, options, args):
145
154
if options .excludes :
146
155
skip .update (options .excludes )
147
156
148
- packages = get_installed_distributions (
149
- local_only = options .local ,
150
- user_only = options .user ,
151
- editables_only = options .editable ,
152
- include_editables = options .include_editable ,
153
- paths = options .path ,
154
- skip = skip ,
155
- )
157
+ packages : "_ProcessedDists" = [
158
+ cast ("_DistWithLatestInfo" , d )
159
+ for d in get_environment (options .path ).iter_installed_distributions (
160
+ local_only = options .local ,
161
+ user_only = options .user ,
162
+ editables_only = options .editable ,
163
+ include_editables = options .include_editable ,
164
+ skip = skip ,
165
+ )
166
+ ]
156
167
157
168
# get_not_required must be called firstly in order to find and
158
169
# filter out all dependencies correctly. Otherwise a package
@@ -170,45 +181,47 @@ def run(self, options, args):
170
181
return SUCCESS
171
182
172
183
def get_outdated (self , packages , options ):
173
- # type: (List[Distribution] , Values) -> List[Distribution]
184
+ # type: (_ProcessedDists , Values) -> _ProcessedDists
174
185
return [
175
186
dist for dist in self .iter_packages_latest_infos (packages , options )
176
- if dist .latest_version > dist .parsed_version
187
+ if dist .latest_version > dist .version
177
188
]
178
189
179
190
def get_uptodate (self , packages , options ):
180
- # type: (List[Distribution] , Values) -> List[Distribution]
191
+ # type: (_ProcessedDists , Values) -> _ProcessedDists
181
192
return [
182
193
dist for dist in self .iter_packages_latest_infos (packages , options )
183
- if dist .latest_version == dist .parsed_version
194
+ if dist .latest_version == dist .version
184
195
]
185
196
186
197
def get_not_required (self , packages , options ):
187
- # type: (List[Distribution], Values) -> List[Distribution]
188
- dep_keys = set () # type: Set[Distribution]
189
- for dist in packages :
190
- dep_keys .update (requirement .key for requirement in dist .requires ())
198
+ # type: (_ProcessedDists, Values) -> _ProcessedDists
199
+ dep_keys = {
200
+ canonicalize_name (dep .name )
201
+ for dist in packages
202
+ for dep in dist .iter_dependencies ()
203
+ }
191
204
192
205
# Create a set to remove duplicate packages, and cast it to a list
193
206
# to keep the return type consistent with get_outdated and
194
207
# get_uptodate
195
- return list ({pkg for pkg in packages if pkg .key not in dep_keys })
208
+ return list ({pkg for pkg in packages if pkg .canonical_name not in dep_keys })
196
209
197
210
def iter_packages_latest_infos (self , packages , options ):
198
- # type: (List[Distribution] , Values) -> Iterator[Distribution ]
211
+ # type: (_ProcessedDists , Values) -> Iterator[_DistWithLatestInfo ]
199
212
with self ._build_session (options ) as session :
200
213
finder = self ._build_package_finder (options , session )
201
214
202
215
def latest_info (dist ):
203
- # type: (Distribution ) -> Distribution
204
- all_candidates = finder .find_all_candidates (dist .key )
216
+ # type: (_DistWithLatestInfo ) -> Optional[_DistWithLatestInfo]
217
+ all_candidates = finder .find_all_candidates (dist .canonical_name )
205
218
if not options .pre :
206
219
# Remove prereleases
207
220
all_candidates = [candidate for candidate in all_candidates
208
221
if not candidate .version .is_prerelease ]
209
222
210
223
evaluator = finder .make_candidate_evaluator (
211
- project_name = dist .project_name ,
224
+ project_name = dist .canonical_name ,
212
225
)
213
226
best_candidate = evaluator .sort_best_candidate (all_candidates )
214
227
if best_candidate is None :
@@ -219,7 +232,6 @@ def latest_info(dist):
219
232
typ = 'wheel'
220
233
else :
221
234
typ = 'sdist'
222
- # This is dirty but makes the rest of the code much cleaner
223
235
dist .latest_version = remote_version
224
236
dist .latest_filetype = typ
225
237
return dist
@@ -229,21 +241,21 @@ def latest_info(dist):
229
241
yield dist
230
242
231
243
def output_package_listing (self , packages , options ):
232
- # type: (List[Distribution] , Values) -> None
244
+ # type: (_ProcessedDists , Values) -> None
233
245
packages = sorted (
234
246
packages ,
235
- key = lambda dist : dist .project_name . lower () ,
247
+ key = lambda dist : dist .canonical_name ,
236
248
)
237
249
if options .list_format == 'columns' and packages :
238
250
data , header = format_for_columns (packages , options )
239
251
self .output_package_listing_columns (data , header )
240
252
elif options .list_format == 'freeze' :
241
253
for dist in packages :
242
254
if options .verbose >= 1 :
243
- write_output ("%s==%s (%s)" , dist .project_name ,
255
+ write_output ("%s==%s (%s)" , dist .canonical_name ,
244
256
dist .version , dist .location )
245
257
else :
246
- write_output ("%s==%s" , dist .project_name , dist .version )
258
+ write_output ("%s==%s" , dist .canonical_name , dist .version )
247
259
elif options .list_format == 'json' :
248
260
write_output (format_for_json (packages , options ))
249
261
@@ -264,7 +276,7 @@ def output_package_listing_columns(self, data, header):
264
276
265
277
266
278
def format_for_columns (pkgs , options ):
267
- # type: (List[Distribution] , Values) -> Tuple[List[List[str]], List[str]]
279
+ # type: (_ProcessedDists , Values) -> Tuple[List[List[str]], List[str]]
268
280
"""
269
281
Convert the package data into something usable
270
282
by output_package_listing_columns.
@@ -277,41 +289,41 @@ def format_for_columns(pkgs, options):
277
289
header = ["Package" , "Version" ]
278
290
279
291
data = []
280
- if options .verbose >= 1 or any (dist_is_editable ( x ) for x in pkgs ):
292
+ if options .verbose >= 1 or any (x . editable for x in pkgs ):
281
293
header .append ("Location" )
282
294
if options .verbose >= 1 :
283
295
header .append ("Installer" )
284
296
285
297
for proj in pkgs :
286
298
# if we're working on the 'outdated' list, separate out the
287
299
# latest_version and type
288
- row = [proj .project_name , proj .version ]
300
+ row = [proj .canonical_name , str ( proj .version ) ]
289
301
290
302
if running_outdated :
291
- row .append (proj .latest_version )
303
+ row .append (str ( proj .latest_version ) )
292
304
row .append (proj .latest_filetype )
293
305
294
- if options .verbose >= 1 or dist_is_editable ( proj ) :
295
- row .append (proj .location )
306
+ if options .verbose >= 1 or proj . editable :
307
+ row .append (proj .location or "" )
296
308
if options .verbose >= 1 :
297
- row .append (get_installer ( proj ) )
309
+ row .append (proj . installer )
298
310
299
311
data .append (row )
300
312
301
313
return data , header
302
314
303
315
304
316
def format_for_json (packages , options ):
305
- # type: (List[Distribution] , Values) -> str
317
+ # type: (_ProcessedDists , Values) -> str
306
318
data = []
307
319
for dist in packages :
308
320
info = {
309
- 'name' : dist .project_name ,
321
+ 'name' : dist .canonical_name ,
310
322
'version' : str (dist .version ),
311
323
}
312
324
if options .verbose >= 1 :
313
- info ['location' ] = dist .location
314
- info ['installer' ] = get_installer ( dist )
325
+ info ['location' ] = dist .location or ""
326
+ info ['installer' ] = dist . installer
315
327
if options .outdated :
316
328
info ['latest_version' ] = str (dist .latest_version )
317
329
info ['latest_filetype' ] = dist .latest_filetype
0 commit comments