1
1
import logging
2
2
import re
3
- from collections import OrderedDict , namedtuple
3
+ from collections import namedtuple
4
+ from typing import Dict , List , Optional , Tuple
4
5
5
6
import requests
6
7
from requests import HTTPError
@@ -25,9 +26,10 @@ def __init__(self, name=None, version=None, force_setup=False):
25
26
super (PyPi , self ).__init__ (name = name , version = version )
26
27
27
28
def _populate_fields_by_distutils (self ):
29
+ # TODO: Implement injection in distutils when there is no PyPi metadata
28
30
pass
29
31
30
- def refresh_section (self , section = "" , force_distutils = False ):
32
+ def refresh_section (self , section : str = "" , force_distutils : bool = False ):
31
33
if self ._force_setup or force_distutils :
32
34
self ._populate_fields_by_distutils ()
33
35
return
@@ -38,7 +40,7 @@ def refresh_section(self, section="", force_distutils=False):
38
40
self ._force_setup = True
39
41
self .refresh_section (section , force_distutils = True )
40
42
41
- def _get_pypi_metadata (self ):
43
+ def _get_pypi_metadata (self ) -> dict :
42
44
if not self .package .version :
43
45
log .info (
44
46
f"Version for { self .package .name } not specified.\n "
@@ -85,36 +87,28 @@ def _get_pypi_metadata(self):
85
87
return self ._pypi_metadata
86
88
87
89
@staticmethod
88
- def get_sha256_from_pypi_metadata (pypi_metadata ) :
90
+ def get_sha256_from_pypi_metadata (pypi_metadata : dict ) -> str :
89
91
for pkg_info in pypi_metadata .get ("urls" ):
90
92
if pkg_info .get ("packagetype" , "" ) == "sdist" :
91
93
return pkg_info ["digests" ]["sha256" ]
92
94
raise ValueError ("Hash information for sdist was not found on PyPi metadata." )
93
95
94
- def _extract_pypi_requirements (self , metadata ) :
96
+ def _extract_pypi_requirements (self , metadata : dict ) -> Requirements :
95
97
if not metadata ["info" ].get ("requires_dist" ):
96
98
return Requirements (host = ["python" , "pip" ], run = ["python" ])
97
- limit_python = metadata ["info" ].get ("requires_python" , "" )
98
- if limit_python is None :
99
- limit_python = ""
100
99
run_req = []
101
100
102
101
for req in metadata ["info" ].get ("requires_dist" ):
103
102
list_raw_requirements = req .split (";" )
104
103
105
104
selector = ""
106
105
if len (list_raw_requirements ) > 1 :
107
- self ._is_using_selectors = True
108
- if limit_python :
109
- self .build .skip = f"true { PyPi .py_version_to_selector (metadata )} "
110
- else :
111
- self .build .skip = None
112
- limit_python = ""
113
106
option , operation , value = PyPi ._get_extra_from_requires_dist (
114
107
list_raw_requirements [1 ]
115
108
)
116
- if value == "testing" :
109
+ if option == "extra" or value == "testing" :
117
110
continue
111
+ self ._is_using_selectors = True
118
112
selector = PyPi ._parse_extra_metadata_to_selector (
119
113
option , operation , value
120
114
)
@@ -123,19 +117,39 @@ def _extract_pypi_requirements(self, metadata):
123
117
)
124
118
run_req .append (f"{ pkg_name } { version } { selector } " .strip ())
125
119
120
+ limit_python = metadata ["info" ].get ("requires_python" , "" )
121
+ if limit_python and self ._is_using_selectors :
122
+ self .build .skip = f"true { PyPi .py_version_to_selector (metadata )} "
123
+ limit_python = ""
124
+ else :
125
+ self .build .skip = None
126
+ limit_python = PyPi .py_version_to_limit_python (metadata )
127
+
126
128
host_req = [f"python{ limit_python } " , "pip" ]
127
129
run_req .insert (0 , f"python{ limit_python } " )
128
130
return Requirements (host = host_req , run = run_req )
129
131
130
132
@staticmethod
131
- def _get_extra_from_requires_dist (string_parse ):
133
+ def _get_extra_from_requires_dist (string_parse : str ) -> Tuple [str , str , str ]:
134
+ """Receives the extra metadata e parse it to get the option, operation
135
+ and value.
136
+
137
+ :param string_parse: metadata extra
138
+ :return: return the option , operation and value of the extra metadata
139
+ """
132
140
option , operation , value = re .match (
133
141
r"^\s*(\w+)\s+(\W*)\s+(.*)" , string_parse , re .DOTALL
134
142
).groups ()
135
143
return option , operation , re .sub (r"['\"]" , "" , value )
136
144
137
145
@staticmethod
138
- def _get_name_version_from_requires_dist (string_parse ):
146
+ def _get_name_version_from_requires_dist (string_parse : str ) -> Tuple [str , str ]:
147
+ """Extract the name and the version from `requires_dist` present in
148
+ PyPi`s metadata
149
+
150
+ :param string_parse: requires_dist value from PyPi metadata
151
+ :return: Name and version of a package
152
+ """
139
153
pkg = re .match (r"^\s*([^\s]+)\s*(\(.*\))?\s*" , string_parse , re .DOTALL )
140
154
pkg_name = pkg .group (1 ).strip ()
141
155
version = ""
@@ -144,7 +158,17 @@ def _get_name_version_from_requires_dist(string_parse):
144
158
return pkg_name .strip (), re .sub (r"[\(\)]" , "" , version ).strip ()
145
159
146
160
@staticmethod
147
- def py_version_to_selector (pypi_metadata ):
161
+ def _generic_py_ver_to (
162
+ pypi_metadata : dict , is_selector : bool = False
163
+ ) -> Optional [str ]:
164
+ """Generic function which abstract the parse of the requires_python
165
+ present in the PyPi metadata. Basically it can generate the selectors
166
+ for Python or the delimiters if it is a `noarch: python` python package
167
+
168
+ :param pypi_metadata: PyPi metadata
169
+ :param is_selector:
170
+ :return:
171
+ """
148
172
req_python = re .findall (
149
173
r"([><=!]+)\s*(\d+)(?:\.(\d+))?" , pypi_metadata ["info" ]["requires_python" ],
150
174
)
@@ -156,24 +180,52 @@ def py_version_to_selector(pypi_metadata):
156
180
if all (all_py ):
157
181
return None
158
182
if all (all_py [1 :]):
159
- return "# [py2k]"
183
+ return (
184
+ "# [py2k]"
185
+ if is_selector
186
+ else f">={ SUPPORTED_PY [1 ].major } .{ SUPPORTED_PY [1 ].minor } "
187
+ )
160
188
if py_ver_enabled .get (PyVer (2 , 7 )) and any (all_py [1 :]) is False :
161
- return "# [py3k]"
189
+ return "# [py3k]" if is_selector else "<3.0"
162
190
163
191
for pos , py_ver in enumerate (SUPPORTED_PY [1 :], 1 ):
164
192
if all (all_py [pos :]) and any (all_py [:pos ]) is False :
165
- return f"# [py<{ py_ver .major } { py_ver .minor } ]"
193
+ return (
194
+ f"# [py<{ py_ver .major } { py_ver .minor } ]"
195
+ if is_selector
196
+ else f">={ py_ver .major } .{ py_ver .minor } "
197
+ )
166
198
elif any (all_py [pos :]) is False :
167
- return f"# [py>={ py_ver .major } { py_ver .minor } ]"
199
+ return (
200
+ f"# [py>={ py_ver .major } { py_ver .minor } ]"
201
+ if is_selector
202
+ else f"<{ py_ver .major } .{ py_ver .minor } "
203
+ )
168
204
169
- all_selector = PyPi ._get_multiple_selectors (py_ver_enabled )
205
+ all_selector = PyPi ._get_multiple_selectors (
206
+ py_ver_enabled , is_selector = is_selector
207
+ )
170
208
if all_selector :
171
- return "# [{}]" .format (" or " .join (all_selector ))
209
+ return (
210
+ "# [{}]" .format (" or " .join (all_selector ))
211
+ if is_selector
212
+ else "," .join (all_selector )
213
+ )
172
214
return None
173
215
174
216
@staticmethod
175
- def _get_py_version_available (req_python ):
176
- py_ver_enabled = OrderedDict ([(py_ver , True ) for py_ver in SUPPORTED_PY ])
217
+ def py_version_to_limit_python (pypi_metadata : dict ) -> Optional [str ]:
218
+ return PyPi ._generic_py_ver_to (pypi_metadata , is_selector = False )
219
+
220
+ @staticmethod
221
+ def py_version_to_selector (pypi_metadata : dict ) -> Optional [str ]:
222
+ return PyPi ._generic_py_ver_to (pypi_metadata , is_selector = True )
223
+
224
+ @staticmethod
225
+ def _get_py_version_available (
226
+ req_python : List [Tuple [str , str , str ]]
227
+ ) -> Dict [PyVer , bool ]:
228
+ py_ver_enabled = {py_ver : True for py_ver in SUPPORTED_PY }
177
229
for op , major , minor in req_python :
178
230
if not minor :
179
231
minor = 0
@@ -186,18 +238,24 @@ def _get_py_version_available(req_python):
186
238
return py_ver_enabled
187
239
188
240
@staticmethod
189
- def _get_multiple_selectors (result ):
241
+ def _get_multiple_selectors (selectors : Dict [ PyVer , bool ], is_selector = False ):
190
242
all_selector = []
191
- if result [PyVer (2 , 7 )] is False :
192
- all_selector . append ( "py2k" )
193
- for py_ver , is_enabled in result .items ():
243
+ if selectors [PyVer (2 , 7 )] is False :
244
+ all_selector += [ "py2k" ] if is_selector else [ ">3.0" ]
245
+ for py_ver , is_enabled in selectors .items ():
194
246
if py_ver == PyVer (2 , 7 ) or is_enabled :
195
247
continue
196
- all_selector .append (f"py=={ py_ver .major } { py_ver .minor } " )
248
+ all_selector += (
249
+ [f"py=={ py_ver .major } { py_ver .minor } " ]
250
+ if is_selector
251
+ else [f"!={ py_ver .major } .{ py_ver .minor } " ]
252
+ )
197
253
return all_selector
198
254
199
255
@staticmethod
200
- def _parse_extra_metadata_to_selector (option , operation , value ):
256
+ def _parse_extra_metadata_to_selector (
257
+ option : str , operation : str , value : str
258
+ ) -> str :
201
259
if option == "extra" :
202
260
return ""
203
261
if option == "python_version" :
0 commit comments