19
19
import pkg_resources
20
20
import re
21
21
import zipfile
22
+ from email .parser import Parser
23
+
24
+
25
+ class WheelMetadata (pkg_resources .FileMetadata ):
26
+ """Metadata handler for Wheels
27
+
28
+ This provider acts like FileMetadata, but returns
29
+ """
30
+
31
+ def __init__ (self , path ):
32
+ self .path = path
33
+
34
+ def get_metadata (self , name ):
35
+ if name != 'METADATA' :
36
+ raise KeyError ("No metadata except METADATA is available" )
37
+
38
+ basename = os .path .basename (self .path )
39
+ parts = basename .split ('-' )
40
+ distribution , version = parts [0 ], parts [1 ]
41
+ metadata_path = '{}-{}.dist-info/METADATA' .format (distribution , version )
42
+
43
+ with zipfile .ZipFile (self .path ) as zf :
44
+ # pkg_resources uses email.parser.Parser to parse METADATA, which doesn't support unicode
45
+ # In order to solve this we have to either reimplement pkg_resources' parsing to not use email.parser
46
+ # or strip Unicode characters. Since PEP 566 specifically references email.parser as the way to read
47
+ # METADATA, stripping Unicode characters seems like the better solution for now, especially since this
48
+ # shouldn't affect any information we care about for dependency resoltuion.
49
+ metadata = zf .read (metadata_path ).decode ('ascii' , 'ignore' )
50
+ return metadata
22
51
23
52
24
53
class Wheel (object ):
25
54
26
55
def __init__ (self , path ):
27
56
self ._path = path
28
-
57
+
58
+ @property
59
+ def _dist (self ):
60
+ try :
61
+ return self .__dist
62
+ except AttributeError :
63
+ metadata = WheelMetadata (self .path ())
64
+ self .__dist = pkg_resources .DistInfoDistribution .from_filename (self .path (), metadata )
65
+ return self .__dist
66
+
29
67
def path (self ):
30
68
return self ._path
31
69
@@ -55,22 +93,14 @@ def _dist_info(self):
55
93
# google_cloud-0.27.0.dist-info
56
94
return '{}-{}.dist-info' .format (self .distribution (), self .version ())
57
95
58
- def metadata (self ):
59
- # Extract the structured data from metadata.json in the WHL's dist-info
60
- # directory.
96
+ def _metadata (self ):
97
+ # Extract the structured data from METADATA file
61
98
with zipfile .ZipFile (self .path (), 'r' ) as whl :
62
- # first check for metadata.json
63
- try :
64
- with whl .open (self ._dist_info () + '/metadata.json' ) as f :
65
- return json .loads (f .read ().decode<
AD86
/span>("utf-8" ))
66
- except KeyError :
67
- pass
68
- # fall back to METADATA file (https://www.python.org/dev/peps/pep-0427/)
69
99
with whl .open (self ._dist_info () + '/METADATA' ) as f :
70
100
return self ._parse_metadata (f .read ().decode ("utf-8" ))
71
101
72
102
def name (self ):
73
- return self .metadata ().get ('name' )
103
+ return self ._metadata ().get ('name' )
74
104
75
105
def dependencies (self , extra = None ):
76
106
"""Access the dependencies of this Wheel.
@@ -82,29 +112,20 @@ def dependencies(self, extra=None):
82
112
Yields:
83
113
the names of requirements from the metadata.json
84
114
"""
85
- # TODO(mattmoor): Is there a schema to follow for this?
86
- dependency_set = set ()
87
-
88
- run_requires = self .metadata ().get ('run_requires' , [])
89
- for requirement in run_requires :
90
- if requirement .get ('extra' ) != extra :
91
- # Match the requirements for the extra we're looking for.
92
- continue
93
- marker = requirement .get ('environment' )
94
- if marker and not pkg_resources .evaluate_marker (marker ):
95
- # The current environment does not match the provided PEP 508 marker,
96
- # so ignore this requirement.
97
- continue
98
- requires = requirement .get ('requires' , [])
99
- for entry in requires :
100
- # Strip off any trailing versioning data.
101
- parts = re .split ('[ ><=()]' , entry )
102
- dependency_set .add (parts [0 ])
115
+ requires = set (self ._dist .requires ())
116
+ if extra :
117
+ requires = set (self ._dist .requires (extras = (extra ,))) - requires
103
118
119
+ dependency_set = set ()
120
+ for r in requires :
121
+ name = r .project_name
122
+ if r .extras :
123
+ name += "[{0}]" .format ("," .join (sorted (r .extras )))
124
+ dependency_set .add (name )
104
125
return dependency_set
105
126
106
127
def extras (self ):
107
- return self .metadata (). get ( ' extras' , [])
128
+ return self ._dist . extras
108
129
109
130
def expand (self , directory ):
110
131
with zipfile .ZipFile (self .path (), 'r' ) as whl :
0 commit comments