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)
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)
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)
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)
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)