1
+ import csv
1
2
import logging
2
3
import os
3
- from email .parser import FeedParser
4
4
from optparse import Values
5
- from typing import Dict , Iterator , List
5
+ from typing import Iterator , List , NamedTuple , Optional
6
6
7
- from pip ._vendor import pkg_resources
8
7
from pip ._vendor .packaging .utils import canonicalize_name
9
8
10
9
from pip ._internal .cli .base_command import Command
11
10
from pip ._internal .cli .status_codes import ERROR , SUCCESS
11
+ from pip ._internal .metadata import BaseDistribution , get_default_environment
12
12
from pip ._internal .utils .misc import write_output
13
13
14
14
logger = logging .getLogger (__name__ )
@@ -50,98 +50,111 @@ def run(self, options, args):
50
50
return SUCCESS
51
51
52
52
53
- def search_packages_info (query ):
54
- # type: (List[str]) -> Iterator[Dict[str, str]]
53
+ class _PackageInfo (NamedTuple ):
54
+ name : str
55
+ version : str
56
+ location : str
57
+ requires : List [str ]
58
+ required_by : List [str ]
59
+ installer : str
60
+ metadata_version : str
61
+ classifiers : List [str ]
62
+ summary : str
63
+ homepage : str
64
+ author : str
65
+ author_email : str
66
+ license : str
67
+ entry_points : List [str ]
68
+ files : Optional [List [str ]]
69
+
70
+
71
+ def search_packages_info (query : List [str ]) -> Iterator [_PackageInfo ]:
55
72
"""
56
73
Gather details from installed distributions. Print distribution name,
57
74
version, location, and installed files. Installed files requires a
58
75
pip generated 'installed-files.txt' in the distributions '.egg-info'
59
76
directory.
60
77
"""
61
- installed = {}
62
- for p in pkg_resources .working_set :
63
- installed [canonicalize_name (p .project_name )] = p
78
+ env = get_default_environment ()
64
79
80
+ installed = {
81
+ dist .canonical_name : dist
82
+ for dist in env .iter_distributions ()
83
+ }
65
84
query_names = [canonicalize_name (name ) for name in query ]
66
85
missing = sorted (
67
86
[name for name , pkg in zip (query , query_names ) if pkg not in installed ]
68
87
)
69
88
if missing :
70
89
logger .warning ('Package(s) not found: %s' , ', ' .join (missing ))
71
90
72
- def get_requiring_packages (package_name ):
73
- # type: (str) -> List[str]
74
- canonical_name = canonicalize_name (package_name )
91
+ def _get_requiring_packages (current_dist : BaseDistribution ) -> List [str ]:
75
92
return [
76
- pkg .project_name for pkg in pkg_resources .working_set
77
- if canonical_name in
78
- [canonicalize_name (required .name ) for required in
79
- pkg .requires ()]
93
+ dist .metadata ["Name" ] or "UNKNOWN"
94
+ for dist in installed .values ()
95
+ if current_dist .canonical_name in {
96
+ canonicalize_name (d .name ) for d in dist .iter_dependencies ()
97
+ }
80
98
]
81
99
82
- for dist in [installed [pkg ] for pkg in query_names if pkg in installed ]:
83
- package = {
84
- 'name' : dist .project_name ,
85
- 'version' : dist .version ,
86
- 'location' : dist .location ,
87
- 'requires' : [dep .project_name for dep in dist .requires ()],
88
- 'required_by' : get_requiring_packages (dist .project_name )
89
- }
90
- file_list = None
91
- metadata = ''
92
- if isinstance (dist , pkg_resources .DistInfoDistribution ):
93
- # RECORDs should be part of .dist-info metadatas
94
- if dist .has_metadata ('RECORD' ):
95
- lines = dist .get_metadata_lines ('RECORD' )
96
- paths = [line .split (',' )[0 ] for line in lines ]
97
- paths = [os .path .join (dist .location , p ) for p in paths ]
98
- file_list = [os .path .relpath (p , dist .location ) for p in paths ]
99
-
100
- if dist .has_metadata ('METADATA' ):
101
- metadata = dist .get_metadata ('METADATA' )
100
+ def _files_from_record (dist : BaseDistribution ) -> Optional [Iterator [str ]]:
101
+ try :
102
+ text = dist .read_text ('RECORD' )
103
+ except FileNotFoundError :
104
+ return None
105
+ return (row [0 ] for row in csv .reader (text .splitlines ()))
106
+
107
+ def _files_from_installed_files (dist : BaseDistribution ) -> Optional [Iterator [str ]]:
108
+ try :
109
+ text = dist .read_text ('installed-files.txt' )
110
+ except FileNotFoundError :
111
+ return None
112
+ return (p for p in text .splitlines (keepends = False ) if p )
113
+
114
+ for query_name in query_names :
115
+ try :
116
+ dist = installed [query_name ]
117
+ except KeyError :
118
+ continue
119
+
120
+ try :
121
+ entry_points_text = dist .read_text ('entry_points.txt' )
122
+ entry_points = entry_points_text .splitlines (keepends = False )
123
+ except FileNotFoundError :
124
+ entry_points = []
125
+
126
+ files_iter = _files_from_record (dist ) or _files_from_installed_files (dist )
127
+ if files_iter is None :
128
+ files : Optional [List [str ]] = None
102
129
else :
103
- # Otherwise use pip's log for .egg-info's
104
- if dist .has_metadata ('installed-files.txt' ):
105
- paths = dist .get_metadata_lines ('installed-files.txt' )
106
- paths = [os .path .join (dist .egg_info , p ) for p in paths ]
107
- file_list = [os .path .relpath (p , dist .location ) for p in paths ]
108
-
109
- if dist .has_metadata ('PKG-INFO' ):
110
- metadata = dist .get_metadata ('PKG-INFO' )
111
-
112
- if dist .has_metadata ('entry_points.txt' ):
113
- entry_points = dist .get_metadata_lines ('entry_points.txt' )
114
- package ['entry_points' ] = entry_points
115
-
116
- if dist .has_metadata ('INSTALLER' ):
117
- for line in dist .get_metadata_lines ('INSTALLER' ):
118
- if line .strip ():
119
- package ['installer' ] = line .strip ()
120
- break
121
-
122
- # @todo: Should pkg_resources.Distribution have a
123
- # `get_pkg_info` method?
124
- feed_parser = FeedParser ()
125
- feed_parser .feed (metadata )
126
- pkg_info_dict = feed_parser .close ()
127
- for key in ('metadata-version' , 'summary' ,
128
- 'home-page' , 'author' , 'author-email' , 'license' ):
129
- package [key ] = pkg_info_dict .get (key )
130
-
131
- # It looks like FeedParser cannot deal with repeated headers
132
- classifiers = []
133
- for line in metadata .splitlines ():
134
- if line .startswith ('Classifier: ' ):
135
- classifiers .append (line [len ('Classifier: ' ):])
136
- package ['classifiers' ] = classifiers
137
-
138
- if file_list :
139
- package ['files' ] = sorted (file_list )
140
- yield package
141
-
142
-
143
- def print_results (distributions , list_files = False , verbose = False ):
144
- # type: (Iterator[Dict[str, str]], bool, bool) -> bool
130
+ files = sorted (os .path .relpath (p , dist .location ) for p in files_iter )
131
+
132
+ metadata = dist .metadata
133
+
134
+ yield _PackageInfo (
135
+ name = dist .raw_name ,
136
+ version = str (dist .version ),
137
+ location = dist .location or "" ,
138
+ requires = [req .name for req in dist .iter_dependencies ()],
139
+ required_by = _get_requiring_packages (dist ),
140
+ installer = dist .installer ,
141
+ metadata_version = dist .metadata_version or "" ,
142
+ classifiers = metadata .get_all ("Classifier" , []),
143
+ summary = metadata .get ("Summary" , "" ),
144
+ homepage = metadata .get ("Home-page" , "" ),
145
+ author = metadata .get ("Author" , "" ),
146
+ author_email = metadata .get ("Author-email" , "" ),
147
+ license = metadata .get ("License" , "" ),
148
+ entry_points = entry_points ,
149
+ files = files ,
150
+ )
151
+
152
+
153
+ def print_results (
154
+ distributions : Iterator [_PackageInfo ],
155
+ list_files : bool ,
156
+ verbose : bool ,
157
+ ) -> bool :
145
158
"""
146
159
Print the information from installed distributions found.
147
160
"""
@@ -151,31 +164,31 @@ def print_results(distributions, list_files=False, verbose=False):
151
164
if i > 0 :
152
165
write_output ("---" )
153
166
154
- write_output ("Name: %s" , dist .get ( ' name' , '' ) )
155
- write_output ("Version: %s" , dist .get ( ' version' , '' ) )
156
- write_output ("Summary: %s" , dist .get ( ' summary' , '' ) )
157
- write_output ("Home-page: %s" , dist .get ( 'home-page' , '' ) )
158
- write_output ("Author: %s" , dist .get ( ' author' , '' ) )
159
- write_output ("Author-email: %s" , dist .get ( 'author-email' , '' ) )
160
- write_output ("License: %s" , dist .get ( ' license' , '' ) )
161
- write_output ("Location: %s" , dist .get ( ' location' , '' ) )
162
- write_output ("Requires: %s" , ', ' .join (dist .get ( ' requires' , []) ))
163
- write_output ("Required-by: %s" , ', ' .join (dist .get ( ' required_by' , []) ))
167
+ write_output ("Name: %s" , dist .name )
168
+ write_output ("Version: %s" , dist .version )
169
+ write_output ("Summary: %s" , dist .summary )
170
+ write_output ("Home-page: %s" , dist .homepage )
171
+ write_output ("Author: %s" , dist .author )
172
+ write_output ("Author-email: %s" , dist .author_email )
173
+ write_output ("License: %s" , dist .license )
174
+ write_output ("Location: %s" , dist .location )
175
+ write_output ("Requires: %s" , ', ' .join (dist .requires ))
176
+ write_output ("Required-by: %s" , ', ' .join (dist .required_by ))
164
177
165
178
if verbose :
166
- write_output ("Metadata-Version: %s" ,
167
- dist .get ('metadata-version' , '' ))
168
- write_output ("Installer: %s" , dist .get ('installer' , '' ))
179
+ write_output ("Metadata-Version: %s" , dist .metadata_version )
180
+ write_output ("Installer: %s" , dist .installer )
169
181
write_output ("Classifiers:" )
170
- for classifier in dist .get ( ' classifiers' , []) :
182
+ for classifier in dist .classifiers :
171
183
write_output (" %s" , classifier )
172
184
write_output ("Entry-points:" )
173
- for entry in dist .get ( ' entry_points' , []) :
185
+ for entry in dist .entry_points :
174
186
write_output (" %s" , entry .strip ())
175
187
if list_files :
176
188
write_output ("Files:" )
177
- for line in dist .get ('files' , []):
178
- write_output (" %s" , line .strip ())
179
- if "files" not in dist :
180
- write_output ("Cannot locate installed-files.txt" )
189
+ if dist .files is None :
190
+ write_output ("Cannot locate RECORD or installed-files.txt" )
191
+ else :
192
+ for line in dist .files :
193
+ write_output (" %s" , line .strip ())
181
194
return results_printed
0 commit comments