def get_pypi_info(name): client = xmlrpclib.ServerProxy(PYPI_XMLRPC) print(client.system.listMethods()) pypi_stable = client.package_releases(name) print(name, pypi_stable) try: return pypi_stable[0] except IndexError: return None
class Package(object): """ A package to be built for conda. Parameters ---------- pypi_name : str Name of the package on PyPI. Note that PyPI is not case sensitive. version: str, optional Version number of the package. ``None``, the default, implies the most recent version visible on PyPI should be used. """ # The class should only need one client for communicating with PyPI client = xmlrpclib.ServerProxy(PYPI_XMLRPC) def __init__(self, pypi_name, version=None): self._pypi_name = pypi_name self.required_version = version self._build = False self._url = None self._md5 = None self._build_platforms = None self._extra_meta = None self._build_pythons = None @property def pypi_name(self): """ Name of the package on PyPI. """ return self._pypi_name @property def conda_name(self): """ Name of the package on binstar (conda), which requires lowercase names. """ return self.pypi_name.lower() @property def required_version(self): """ Version number of the package. """ return self._required_version @required_version.setter def required_version(self, value): self._required_version = value.strip() @property def build(self): """ bool: ``True`` if this package needs to be built. """ return self._build @build.setter def build(self, value): # TODO: Make sure this is a bool self._build = value @property def is_dev(self): return not (re.search('[a-zA-Z]', self.required_version) is None) @property def url(self): if not self._url: self._retrieve_package_metadata() return self._url @property def md5(self): if not self._md5: self._retrieve_package_metadata() return self._md5 @property def filename(self): return self.url.split('/')[-1] @property def build_platforms(self): """ Return list of platforms on which this package can be built. Defaults to the value of ``ALL_PLATFORMS``. Checks for build information by looking at recipe *templates*, which is probably not really the way to go...might be more generalizable if it looked at recipes instead. """ # Lazy memoization... if self._build_platforms: return self._build_platforms platform_info = self.extra_meta try: platforms = platform_info['extra']['platforms'] except KeyError: platforms = ALL_PLATFORMS self._build_platforms = platforms return self._build_platforms @property def build_pythons(self): if self._build_pythons: return self._build_pythons try: pythons = self.extra_meta['extra']['pythons'] except KeyError: pythons = ["27", "34"] # Make sure version is always a string so it can be compared # to CONDA_PY later. self._build_pythons = [str(p) for p in pythons] return self._build_pythons @property def extra_meta(self): """ The 'extra' metadata, for now read in from meta.yaml. """ if self._extra_meta is not None: return self._extra_meta template_dir = os.path.join(TEMPLATE_FOLDER, self.conda_name) try: meta = render_template(self, 'meta.yaml') except TemplateNotFound as e: # No recipe, make an empty meta for now. meta = '' platform_info = yaml.safe_load(meta) if meta else {} self._extra_meta = platform_info return self._extra_meta def _retrieve_package_metadata(self): """ Get URL and md5 checksum from PyPI for either the specified version or the most recent version. """ if not self.required_version: version = get_pypi_info(self.pypi_name) else: version = self.required_version urls = self.client.release_urls(self.pypi_name, version) try: # Many packages now have wheels, need to iterate over download # URLs to get the source distribution. for a_url in urls: if a_url['packagetype'] == 'sdist': url = a_url['url'] md5sum = a_url['md5_digest'] break else: # No source distribution, so raise an index error raise IndexError except IndexError: # Apparently a pypi release isn't required to have any source? # If it doesn't, then return None print('No source found for {}: {}'.format(self.pypi_name, self.required_version)) url = None md5sum = None self._url = url self._md5 = md5sum def download(self, directory, checksum=True): """ Download package and store in directory. Parameters ---------- directory : str Directory in which to store the downloaded package. checksum: bool, optional If ``True``, check the MD5 checksum of the download. """ loader = six.moves.urllib.request.URLopener() destination = os.path.join(directory, self.filename) print(destination) loader.retrieve(self.url, destination) if checksum: with open(destination, 'rb') as f: # Not worried about the packages being too big for memory. contents = f.read() md5_downloaded = hashlib.md5(contents).hexdigest() if md5_downloaded != self.md5: raise ValueError('checksum mismatch ' 'in {}'.format(self.filename))
class Package(object): """ A package to be built for conda. Parameters ---------- pypi_name : str Name of the package on PyPI. Note that PyPI is not case sensitive. version: str, optional Version number of the package. ``None``, the default, implies the most recent version visible on PyPI should be used. """ # The class should only need one client for communicating with PyPI client = xmlrpclib.ServerProxy(PYPI_XMLRPC) def __init__(self, pypi_name, version=None): self._pypi_name = pypi_name self.required_version = version self._build = False self._url = None self._md5 = None @property def pypi_name(self): """ Name of the package on PyPI. """ return self._pypi_name @property def conda_name(self): """ Name of the package on binstar (conda), which requires lowercase names. """ return self.pypi_name.lower() @property def required_version(self): """ Version number of the package. """ return self._required_version @required_version.setter def required_version(self, value): self._required_version = value.strip() @property def build(self): """ bool: ``True`` if this package needs to be built. """ return self._build @build.setter def build(self, value): # TODO: Make sure this is a bool self._build = value @property def is_dev(self): return not (re.search('[a-zA-Z]', self.required_version) is None) @property def url(self): if not self._url: self._retrieve_package_metadata() return self._url @property def md5(self): if not self._md5: self._retrieve_package_metadata() return self._md5 @property def filename(self): return self.url.split('/')[-1] def _retrieve_package_metadata(self): """ Get URL and md5 checksum from PyPI for either the specified version or the most recent version. """ if not self.required_version: version = get_pypi_info(self.pypi_name) else: version = self.required_version urls = self.client.release_urls(self.pypi_name, version) try: url = urls[0]['url'] md5sum = urls[0]['md5_digest'] except IndexError: # Apparently a pypi release isn't required to have any source? # If it doesn't, then return None print('No source found for {}: {}'.format(self.pypi_name, self.required_version)) url = None md5sum = None self._url = url self._md5 = md5sum def download(self, directory, checksum=True): """ Download package and store in directory. Parameters ---------- directory : str Directory in which to store the downloaded package. checksum: bool, optional If ``True``, check the MD5 checksum of the download. """ loader = six.moves.urllib.request.URLopener() destination = os.path.join(directory, self.filename) print(destination) loader.retrieve(self.url, destination) if checksum: with open(destination, 'rb') as f: # Not worried about the packages being too big for memory. contents = f.read() md5_downloaded = hashlib.md5(contents).hexdigest() if md5_downloaded != self.md5: raise ValueError('checksum mismatch ' 'in {}'.format(self.filename))