11# encoding: utf-8
22import hashlib
3+ import json
34import logging
45from copy import copy
56from slimurl import URL
1617log = logging .getLogger (__name__ )
1718
1819
20+ def async_retrying (number , exceptions = (Exception ,)):
21+ def decorator (func ):
22+ @coroutine
23+ def wrap (* args , ** kwargs ):
24+ last_exc = None
25+ for i in range (number ):
26+ try :
27+ raise Return ((yield func (* args , ** kwargs )))
28+ except Return :
29+ raise
30+ except exceptions as e :
31+ log .exception ("Error on attempt: %r" , i )
32+ last_exc = e
33+
34+ if last_exc :
35+ raise last_exc
36+ return wrap
37+ return decorator
38+
39+
1940def normalize_package_name (name ):
2041 return name .lower ().replace ("_" , "-" ).replace ("." , "-" )
2142
@@ -26,19 +47,20 @@ class PYPIClient(object):
2647 THREAD_POOL = None
2748 INDEX = None
2849 XMLRPC = None
50+ RPC_URL = None
2951 LOCK = None
3052
3153 @classmethod
3254 def configure (cls , backend , thread_pool ):
3355 cls .CLIENT = AsyncHTTPClient (io_loop = IOLoop .current ())
3456 cls .BACKEND = backend
3557 cls .THREAD_POOL = thread_pool
36- cls .XMLRPC = ServerProxy (
37- str (copy (backend )(path = "/pypi" )),
38- )
58+ cls .RPC_URL = copy (backend )(path = "/pypi" )
59+ cls .XMLRPC = ServerProxy (str (cls .RPC_URL ))
3960 cls .LOCK = Lock ()
4061
4162 @classmethod
63+ @async_retrying (5 )
4264 @coroutine
4365 @Cache (HOUR , files_cache = True , ignore_self = True )
4466 def packages (cls ):
@@ -54,6 +76,7 @@ def packages(cls):
5476 raise Return (index )
5577
5678 @classmethod
79+ @async_retrying (5 )
5780 @coroutine
5881 @Cache (4 * HOUR , files_cache = True , ignore_self = True )
5982 def search (cls , names , descriptions , operator = "or" ):
@@ -62,6 +85,7 @@ def search(cls, names, descriptions, operator="or"):
6285 raise Return (result )
6386
6487 @classmethod
88+ @async_retrying (5 )
6589 @coroutine
6690 def exists (cls , name ):
6791 try :
@@ -76,6 +100,7 @@ def exists(cls, name):
76100 raise Return (True )
77101
78102 @classmethod
103+ @async_retrying (5 )
79104 @coroutine
80105 def find_real_name (cls , name ):
81106 if not options .pypi_proxy :
@@ -92,6 +117,7 @@ def find_real_name(cls, name):
92117 raise Return (real_name )
93118
94119 @classmethod
120+ @async_retrying (5 )
95121 @coroutine
96122 @Cache (4 * HOUR , files_cache = True , ignore_self = True )
97123 def releases (cls , name ):
@@ -119,15 +145,20 @@ def releases(cls, name):
119145 raise Return (set (res ))
120146
121147 @classmethod
148+ @async_retrying (5 )
122149 @coroutine
123150 @Cache (MONTH , files_cache = True , ignore_self = True )
124151 def release_data (cls , name , version ):
125- info , files = yield [
126- cls .XMLRPC .release_data (str (name ), str (version )),
127- cls .XMLRPC .release_urls (str (name ), str (version ))
128- ]
152+ url = copy (cls .RPC_URL )
153+ url .path_append (str (name ), str (version ), 'json' )
154+ log .info ("Gathering info %s" , url )
155+
156+ response = json .loads ((yield cls .CLIENT .fetch (str (url ))).body )
157+ info = response ['info' ]
158+ files = response ['urls' ]
129159
130160 download_url = info .get ('download_url' )
161+
131162 if download_url and not files :
132163 try :
133164 url = URL (download_url )
0 commit comments