mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	
		
			
	
	
		
			100 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			100 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | """Convenient client for all PyPI APIs.
 | ||
|  | 
 | ||
|  | This module provides a ClientWrapper class which will use the "simple" | ||
|  | or XML-RPC API to request information or files from an index. | ||
|  | """
 | ||
|  | 
 | ||
|  | from packaging.pypi import simple, xmlrpc | ||
|  | 
 | ||
|  | _WRAPPER_MAPPINGS = {'get_release': 'simple', | ||
|  |                      'get_releases': 'simple', | ||
|  |                      'search_projects': 'simple', | ||
|  |                      'get_metadata': 'xmlrpc', | ||
|  |                      'get_distributions': 'simple'} | ||
|  | 
 | ||
|  | _WRAPPER_INDEXES = {'xmlrpc': xmlrpc.Client, | ||
|  |                     'simple': simple.Crawler} | ||
|  | 
 | ||
|  | 
 | ||
|  | def switch_index_if_fails(func, wrapper): | ||
|  |     """Decorator that switch of index (for instance from xmlrpc to simple)
 | ||
|  |     if the first mirror return an empty list or raises an exception. | ||
|  |     """
 | ||
|  |     def decorator(*args, **kwargs): | ||
|  |         retry = True | ||
|  |         exception = None | ||
|  |         methods = [func] | ||
|  |         for f in wrapper._indexes.values(): | ||
|  |             if f != func.__self__ and hasattr(f, func.__name__): | ||
|  |                 methods.append(getattr(f, func.__name__)) | ||
|  |         for method in methods: | ||
|  |             try: | ||
|  |                 response = method(*args, **kwargs) | ||
|  |                 retry = False | ||
|  |             except Exception as e: | ||
|  |                 exception = e | ||
|  |             if not retry: | ||
|  |                 break | ||
|  |         if retry and exception: | ||
|  |             raise exception | ||
|  |         else: | ||
|  |             return response | ||
|  |     return decorator | ||
|  | 
 | ||
|  | 
 | ||
|  | class ClientWrapper: | ||
|  |     """Wrapper around simple and xmlrpc clients,
 | ||
|  | 
 | ||
|  |     Choose the best implementation to use depending the needs, using the given | ||
|  |     mappings. | ||
|  |     If one of the indexes returns an error, tries to use others indexes. | ||
|  | 
 | ||
|  |     :param index: tell which index to rely on by default. | ||
|  |     :param index_classes: a dict of name:class to use as indexes. | ||
|  |     :param indexes: a dict of name:index already instantiated | ||
|  |     :param mappings: the mappings to use for this wrapper | ||
|  |     """
 | ||
|  | 
 | ||
|  |     def __init__(self, default_index='simple', index_classes=_WRAPPER_INDEXES, | ||
|  |                  indexes={}, mappings=_WRAPPER_MAPPINGS): | ||
|  |         self._projects = {} | ||
|  |         self._mappings = mappings | ||
|  |         self._indexes = indexes | ||
|  |         self._default_index = default_index | ||
|  | 
 | ||
|  |         # instantiate the classes and set their _project attribute to the one | ||
|  |         # of the wrapper. | ||
|  |         for name, cls in index_classes.items(): | ||
|  |             obj = self._indexes.setdefault(name, cls()) | ||
|  |             obj._projects = self._projects | ||
|  |             obj._index = self | ||
|  | 
 | ||
|  |     def __getattr__(self, method_name): | ||
|  |         """When asking for methods of the wrapper, return the implementation of
 | ||
|  |         the wrapped classes, depending the mapping. | ||
|  | 
 | ||
|  |         Decorate the methods to switch of implementation if an error occurs | ||
|  |         """
 | ||
|  |         real_method = None | ||
|  |         if method_name in _WRAPPER_MAPPINGS: | ||
|  |             obj = self._indexes[_WRAPPER_MAPPINGS[method_name]] | ||
|  |             real_method = getattr(obj, method_name) | ||
|  |         else: | ||
|  |             # the method is not defined in the mappings, so we try first to get | ||
|  |             # it via the default index, and rely on others if needed. | ||
|  |             try: | ||
|  |                 real_method = getattr(self._indexes[self._default_index], | ||
|  |                                       method_name) | ||
|  |             except AttributeError: | ||
|  |                 other_indexes = [i for i in self._indexes | ||
|  |                                  if i != self._default_index] | ||
|  |                 for index in other_indexes: | ||
|  |                     real_method = getattr(self._indexes[index], method_name, | ||
|  |                                           None) | ||
|  |                     if real_method: | ||
|  |                         break | ||
|  |         if real_method: | ||
|  |             return switch_index_if_fails(real_method, self) | ||
|  |         else: | ||
|  |             raise AttributeError("No index have attribute '%s'" % method_name) |