Exemple #1
0
    def transform_name(self, python_package_name, *extras):
        """
        Transform Python package name to Debian package name.

        :param python_package_name: The name of a Python package
                                    as found on PyPI (a string).
        :param extras: Any extras requested to be included (a tuple of strings).
        :returns: The transformed name (a string).

        Examples:

        >>> from py2deb.converter import PackageConverter
        >>> converter = PackageConverter()
        >>> converter.transform_name('example')
        'python-example'
        >>> converter.set_name_prefix('my-custom-prefix')
        >>> converter.transform_name('example')
        'my-custom-prefix-example'
        >>> converter.set_name_prefix('some-web-app')
        >>> converter.transform_name('raven', 'flask')
        'some-web-app-raven-flask'

        """
        key = python_package_name.lower()
        # Check for a system package override by the caller.
        debian_package_name = self.system_packages.get(key)
        if debian_package_name:
            # We don't modify the names of system packages.
            return debian_package_name
        # Check for a package rename override by the caller.
        debian_package_name = self.name_mapping.get(key)
        if not debian_package_name:
            # No override. Make something up :-).
            with_name_prefix = '%s-%s' % (self.name_prefix,
                                          python_package_name)
            normalized_words = normalize_package_name(with_name_prefix).split(
                '-')
            debian_package_name = '-'.join(
                compact_repeating_words(normalized_words))
        # If a requirement includes extras this changes the dependencies of the
        # package. Because Debian doesn't have this concept we encode the names
        # of the extras in the name of the package.
        if extras:
            sorted_extras = sorted(extra.lower() for extra in extras)
            debian_package_name = '%s-%s' % (debian_package_name,
                                             '-'.join(sorted_extras))
        # Always normalize the package name (even if it was given to us by the caller).
        return normalize_package_name(debian_package_name)
Exemple #2
0
    def transform_name(self, python_package_name, *extras):
        """
        Transform Python package name to Debian package name.

        :param python_package_name: The name of a Python package
                                    as found on PyPI (a string).
        :param extras: Any extras requested to be included (a tuple of strings).
        :returns: The transformed name (a string).

        Examples:

        >>> from py2deb import PackageConverter
        >>> converter = PackageConverter()
        >>> converter.transform_name('example')
        'python-example'
        >>> converter.set_name_prefix('my-custom-prefix')
        >>> converter.transform_name('example')
        'my-custom-prefix-example'
        >>> converter.set_name_prefix('some-web-app')
        >>> converter.transform_name('raven', 'flask')
        'some-web-app-raven-flask'

        """
        # Check for an override by the caller.
        debian_package_name = self.name_mapping.get(python_package_name.lower())
        if not debian_package_name:
            # No override. Make something up :-).
            with_name_prefix = '%s-%s' % (self.name_prefix, python_package_name)
            normalized_words = normalize_package_name(with_name_prefix).split('-')
            debian_package_name = '-'.join(compact_repeating_words(normalized_words))
        # If a requirement includes extras this changes the dependencies of the
        # package. Because Debian doesn't have this concept we encode the names
        # of the extras in the name of the package.
        if extras:
            sorted_extras = sorted(extra.lower() for extra in extras)
            debian_package_name = '%s-%s' % (debian_package_name, '-'.join(sorted_extras))
        # Always normalize the package name (even if it was given to us by the caller).
        return normalize_package_name(debian_package_name)
Exemple #3
0
    def transform_name(self, python_package_name, *extras):
        """
        Transform Python package name to Debian package name.

        :param python_package_name: The name of a Python package
                                    as found on PyPI (a string).
        :param extras: Any extras requested to be included (a tuple of strings).
        :returns: The transformed name (a string).

        Examples:

        >>> from py2deb.converter import PackageConverter
        >>> converter = PackageConverter()
        >>> converter.transform_name('example')
        'python-example'
        >>> converter.set_name_prefix('my-custom-prefix')
        >>> converter.transform_name('example')
        'my-custom-prefix-example'
        >>> converter.set_name_prefix('some-web-app')
        >>> converter.transform_name('raven', 'flask')
        'some-web-app-raven-flask'

        """
        key = python_package_name.lower()
        # Check for a system package override by the caller.
        debian_package_name = self.system_packages.get(key)
        if debian_package_name:
            # We don't modify the names of system packages.
            return debian_package_name
        # Check for a package rename override by the caller.
        debian_package_name = self.name_mapping.get(key)
        if not debian_package_name:
            # No override. Make something up :-).
            debian_package_name = convert_package_name(
                python_package_name=python_package_name,
                name_prefix=self.name_prefix,
                extras=extras,
            )
        # Always normalize the package name (even if it was given to us by the caller).
        return normalize_package_name(debian_package_name)
Exemple #4
0
    def transform_version(self, package_to_convert, python_requirement_name, python_requirement_version):
        """
        Transform a Python requirement version to a Debian version number.

        :param package_to_convert: The :class:`.PackageToConvert` whose
                                   requirement is being transformed.
        :param python_requirement_name: The name of a Python package
                                        as found on PyPI (a string).
        :param python_requirement_version: The required version of the
                                           Python package (a string).
        :returns: The transformed version (a string).

        This method is a wrapper for :func:`.normalize_package_version()` that
        takes care of one additional quirk to ensure compatibility with
        :pypi:`pip`. Explaining this quirk requires a bit of context:

        - When package A requires package B (via ``install_requires``) and
          package A absolutely pins the required version of package B using one
          or more trailing zeros (e.g. ``B==1.0.0``) but the actual version
          number of package B (embedded in the metadata of package B) contains
          less trailing zeros (e.g. ``1.0``) then :pypi:`pip` will not complain
          but silently fetch version ``1.0`` of package B to satisfy the
          requirement.

        - However this doesn't change the absolutely pinned version in the
          ``install_requires`` metadata of package A.

        - When py2deb converts the resulting requirement set, the dependency of
          package A is converted as ``B (= 1.0.0)``. The resulting packages
          will not be installable because :man:`apt` considers ``1.0`` to be
          different from ``1.0.0``.

        This method analyzes the requirement set to identify occurrences of
        this quirk and strip trailing zeros in ``install_requires`` metadata
        that would otherwise result in converted packages that cannot be
        installed.
        """
        matching_packages = [
            pkg for pkg in self.packages_to_convert
            if package_names_match(pkg.python_name, python_requirement_name)
        ]
        if len(matching_packages) > 1:
            # My assumption while writing this code is that this should never
            # happen. This check is to make sure that if it does happen it will
            # be noticed because the last thing I want is for this `hack' to
            # result in packages that are silently wrongly converted.
            normalized_name = normalize_package_name(python_requirement_name)
            num_matches = len(matching_packages)
            raise Exception(compact("""
                Expected requirement set to contain exactly one Python package
                whose name can be normalized to {name} but encountered {count}
                packages instead! (matching packages: {matches})
            """, name=normalized_name, count=num_matches, matches=matching_packages))
        elif matching_packages:
            # Check whether the version number included in the requirement set
            # matches the version number in a package's requirements.
            requirement_to_convert = matching_packages[0]
            if python_requirement_version != requirement_to_convert.python_version:
                logger.debug("Checking whether to strip trailing zeros from required version ..")
                # Check whether the version numbers share the same prefix.
                required_version = tokenize_version(python_requirement_version)
                included_version = tokenize_version(requirement_to_convert.python_version)
                common_length = min(len(required_version), len(included_version))
                required_prefix = required_version[:common_length]
                included_prefix = included_version[:common_length]
                prefixes_match = (required_prefix == included_prefix)
                logger.debug("Prefix of required version: %s", required_prefix)
                logger.debug("Prefix of included version: %s", included_prefix)
                logger.debug("Prefixes match? %s", prefixes_match)
                # Check if 1) only the required version has a suffix and 2) this
                # suffix consists only of trailing zeros.
                required_suffix = required_version[common_length:]
                included_suffix = included_version[common_length:]
                logger.debug("Suffix of required version: %s", required_suffix)
                logger.debug("Suffix of included version: %s", included_suffix)
                if prefixes_match and required_suffix and not included_suffix:
                    # Check whether the suffix of the required version contains
                    # only zeros, i.e. pip considers the version numbers the same
                    # although apt would not agree.
                    if all(re.match('^0+$', t) for t in required_suffix if t.isdigit()):
                        modified_version = ''.join(required_prefix)
                        logger.warning("Stripping superfluous trailing zeros from required"
                                       " version of %s required by %s! (%s -> %s)",
                                       python_requirement_name, package_to_convert.python_name,
                                       python_requirement_version, modified_version)
                        python_requirement_version = modified_version
        return normalize_package_version(python_requirement_version, prerelease_workaround=self.prerelease_workaround)
Exemple #5
0
    def transform_version(self, package_to_convert, python_requirement_name, python_requirement_version):
        """
        Transform a Python requirement version to a Debian version number.

        :param package_to_convert: The :class:`.PackageToConvert` whose
                                   requirement is being transformed.
        :param python_requirement_name: The name of a Python package
                                        as found on PyPI (a string).
        :param python_requirement_version: The required version of the
                                           Python package (a string).
        :returns: The transformed version (a string).

        This method is a wrapper for :func:`.normalize_package_version()` that
        takes care of one additional quirk to ensure compatibility with pip.
        Explaining this quirk requires a bit of context:

        - When package A requires package B (via ``install_requires``) and
          package A absolutely pins the required version of package B using one
          or more trailing zeros (e.g. ``B==1.0.0``) but the actual version
          number of package B (embedded in the metadata of package B) contains
          less trailing zeros (e.g. ``1.0``) then pip will not complain but
          silently fetch version ``1.0`` of package B to satisfy the
          requirement.

        - However this doesn't change the absolutely pinned version in the
          ``install_requires`` metadata of package A.

        - When py2deb converts the resulting requirement set, the dependency of
          package A is converted as ``B (= 1.0.0)``. The resulting packages
          will not be installable because ``apt`` considers ``1.0`` to be
          different from ``1.0.0``.

        This method analyzes the requirement set to identify occurrences of
        this quirk and strip trailing zeros in ``install_requires`` metadata
        that would otherwise result in converted packages that cannot be
        installed.
        """
        matching_packages = [p for p in self.packages_to_convert
                             if package_names_match(p.python_name, python_requirement_name)]
        if len(matching_packages) != 1:
            # My assumption while writing this code is that this should never
            # happen. This check is to make sure that if it does happen it will
            # be noticed because the last thing I want is for this `hack' to
            # result in packages that are silently wrongly converted.
            normalized_name = normalize_package_name(python_requirement_name)
            num_matches = len(matching_packages)
            raise Exception(compact("""
                Expected requirement set to contain exactly one Python package
                whose name can be normalized to {name} but encountered {count}
                packages instead! (matching packages: {matches})
            """, name=normalized_name, count=num_matches, matches=matching_packages))
        # Check whether the version number included in the requirement set
        # matches the version number in a package's requirements.
        requirement_to_convert = matching_packages[0]
        if python_requirement_version != requirement_to_convert.python_version:
            logger.debug("Checking whether to strip trailing zeros from required version ..")
            # Check whether the version numbers share the same prefix.
            required_version = tokenize_version(python_requirement_version)
            included_version = tokenize_version(requirement_to_convert.python_version)
            common_length = min(len(required_version), len(included_version))
            required_prefix = required_version[:common_length]
            included_prefix = included_version[:common_length]
            prefixes_match = (required_prefix == included_prefix)
            logger.debug("Prefix of required version: %s", required_prefix)
            logger.debug("Prefix of included version: %s", included_prefix)
            logger.debug("Prefixes match? %s", prefixes_match)
            # Check if 1) only the required version has a suffix and 2) this
            # suffix consists only of trailing zeros.
            required_suffix = required_version[common_length:]
            included_suffix = included_version[common_length:]
            logger.debug("Suffix of required version: %s", required_suffix)
            logger.debug("Suffix of included version: %s", included_suffix)
            if prefixes_match and required_suffix and not included_suffix:
                # Check whether the suffix of the required version contains
                # only zeros, i.e. pip considers the version numbers the same
                # although apt would not agree.
                if all(re.match('^0+$', t) for t in required_suffix if t.isdigit()):
                    modified_version = ''.join(required_prefix)
                    logger.warning("Stripping superfluous trailing zeros from required"
                                   " version of %s required by %s! (%s -> %s)",
                                   python_requirement_name, package_to_convert.python_name,
                                   python_requirement_version, modified_version)
                    python_requirement_version = modified_version
        return normalize_package_version(python_requirement_version)