Source code for okonomiyaki.file_formats._egg_info

import posixpath
import re

import six
import zipfile2

from attr import attr, attributes
from attr.validators import instance_of, optional

from ..errors import (
    InvalidRequirementString, InvalidEggName, InvalidMetadataField,
    MissingMetadata, UnsupportedMetadata
)
from ..platforms import (
    EPDPlatform, PlatformABI, PythonABI, PythonImplementation
)
from ..platforms.legacy import LegacyEPDPlatform
from ..utils import (
    compute_sha256, decode_if_needed, encode_if_needed, parse_assignments
)
from ..utils.py3compat import StringIO, string_types
from ..versions import EnpkgVersion, MetadataVersion
from .legacy import (
    _guess_abi_tag, _guess_platform_abi, _guess_platform_tag, _guess_python_tag
)
from ._blacklist import (
    EGG_PLATFORM_BLACK_LIST, EGG_PYTHON_TAG_BLACK_LIST,
    may_be_in_platform_blacklist, may_be_in_python_tag_blacklist,
    may_be_in_pkg_info_blacklist
)
from ._package_info import (
    PackageInfo, _convert_if_needed, _keep_position, _read_pkg_info
)


_EGG_NAME_RE = re.compile("""
    (?P<name>[\.\w]+)
    -
    (?P<version>[^-]+)
    -
    (?P<build>\d+)
    \.egg$""", re.VERBOSE)

EGG_INFO_PREFIX = "EGG-INFO"

# Those may need to be public, depending on how well we can hide their
# locations or not.
_INFO_JSON_LOCATION = posixpath.join(EGG_INFO_PREFIX, "info.json")
_SPEC_DEPEND_LOCATION = posixpath.join(EGG_INFO_PREFIX, "spec", "depend")
_SPEC_LIB_DEPEND_LOCATION = posixpath.join(EGG_INFO_PREFIX, "spec",
                                           "lib-depend")
_SPEC_SUMMARY_LOCATION = posixpath.join(EGG_INFO_PREFIX, "spec", "summary")
_USR_PREFIX_LOCATION = posixpath.join(EGG_INFO_PREFIX, "usr")

_TAG_METADATA_VERSION = "metadata_version"
_TAG_NAME = "name"
_TAG_VERSION = "version"
_TAG_BUILD = "build"
_TAG_ARCH = "arch"
_TAG_OSDIST = "osdist"
_TAG_PLATFORM = "platform"
_TAG_PYTHON = "python"
_TAG_PYTHON_PEP425_TAG = "python_tag"
_TAG_ABI_PEP425_TAG = "abi_tag"
_TAG_PLATFORM_PEP425_TAG = "platform_tag"
_TAG_PLATFORM_ABI = "platform_abi"
_TAG_PACKAGES = "packages"

M = MetadataVersion.from_string

_METADATA_VERSION_TO_KEYS = {
    M("1.1"): (
        _TAG_METADATA_VERSION, _TAG_NAME, _TAG_VERSION, _TAG_BUILD, _TAG_ARCH,
        _TAG_PLATFORM, _TAG_OSDIST, _TAG_PYTHON, _TAG_PACKAGES
    ),
}
_METADATA_VERSION_TO_KEYS[M("1.2")] = \
    _METADATA_VERSION_TO_KEYS[M("1.1")] + (_TAG_PYTHON_PEP425_TAG, )

_METADATA_VERSION_TO_KEYS[M("1.3")] = (
    _METADATA_VERSION_TO_KEYS[M("1.2")] +
    (_TAG_ABI_PEP425_TAG, _TAG_PLATFORM_PEP425_TAG)
)

_METADATA_VERSION_TO_KEYS[M("1.4")] = (
    _METADATA_VERSION_TO_KEYS[M("1.3")] + (_TAG_PLATFORM_ABI, )
)

_UNSUPPORTED = "unsupported"


def _are_compatible(left, right):
    """Return True if both arguments are compatible metadata versions.

    Parameters
    ----------
    left: MetadataVersion
    right: MetadataVersion
    """
    return left.major == right.major


def _highest_compatible(metadata_version):
    """ Returns the highest metadata version supporting that is compatible with
    the given version.
    """
    compatible_versions = [
        m for m in _METADATA_VERSION_TO_KEYS
        if _are_compatible(m, metadata_version)
    ]

    if len(compatible_versions) > 0:
        return max(compatible_versions)
    else:
        raise UnsupportedMetadata(metadata_version)


def split_egg_name(s):
    m = _EGG_NAME_RE.match(s)
    if m is None:
        raise InvalidEggName(s)
    else:
        name, version, build = m.groups()
        return name, version, int(build)


def parse_rawspec(spec_string):
    spec = parse_assignments(StringIO(spec_string.replace('\r', '')))

    metadata_version_string = spec.get(_TAG_METADATA_VERSION)
    if metadata_version_string is not None:
        metadata_version = MetadataVersion.from_string(metadata_version_string)
    else:
        metadata_version = None

    if metadata_version is None:
        raise InvalidMetadataField('metadata_version', metadata_version_string)
    elif metadata_version not in _METADATA_VERSION_TO_KEYS:
        metadata_version = _highest_compatible(metadata_version)

    res = {}

    keys = _METADATA_VERSION_TO_KEYS.get(metadata_version)
    for key in keys:
        try:
            res[key] = spec[key]
        except KeyError:
            raise InvalidMetadataField(key, InvalidMetadataField.undefined)

    for k, v in res.items():
        # Some values are not string-like, so filter on the type that needs
        # conversion
        if isinstance(v, six.binary_type):
            res[k] = decode_if_needed(v)

    res[_TAG_PACKAGES] = [decode_if_needed(v) for v in res[_TAG_PACKAGES]]
    return res


def egg_name(name, version, build):
    """
    Return the egg filename (including the .egg extension) for the given
    arguments
    """
    return "{0}-{1}-{2}.egg".format(name, version, build)


def is_egg_name_valid(s):
    """
    Return True if the given string is a valid egg name (not including the
    .egg, e.g. 'Qt-4.8.5-2')
    """
    return _EGG_NAME_RE.match(s) is not None


_INVALID_REQUIREMENTS = {
    u"numpy-1.8.0": u"numpy 1.8.0",
}


def _translate_invalid_requirement(s):
    return _INVALID_REQUIREMENTS.get(s, s)


def text_attr(**kw):
    """ An attrs.attr-like descriptor to describe fields that must be unicode.
    """
    for k in ("validator", ):
        if k in kw:
            raise ValueError("Cannot pass '{0}' argument".format(k))
    return attr(validator=instance_of(six.text_type), **kw)


def text_or_none_attr(**kw):
    """ An attrs.attr-like descriptor to describe fields that must be unicode
    or None.
    """
    for k in ("validator", ):
        if k in kw:
            raise ValueError("Cannot pass '{0}' argument".format(k))
    return attr(validator=optional(instance_of(six.text_type)), **kw)


@six.python_2_unicode_compatible
@attributes(frozen=True)
class Requirement(object):
    """
    Model for entries in the package metadata inside EGG-INFO/spec/depend
    """
    name = text_attr(default=u"")
    version_string = text_attr(default=u"")
    build_number = attr(-1, validator=instance_of(int))

    @property
    def strictness(self):
        if len(self.version_string) == 0:
            return 1
        elif self.build_number < 0:
            return 2
        else:
            return 3

    @classmethod
    def from_string(cls, s, strictness=2):
        """
        Create a Requirement from string following a name-version-build
        format.

        Parameters
        ----------
        s: text type
            Egg name, e.g. u'Qt-4.8.5-2'.
        strictness: int
            Control strictness of string representation
        """
        name, version, build = split_egg_name(u"{0}.egg".format(s))
        if strictness >= 3:
            build_number = build
        else:
            build_number = -1

        if strictness >= 2:
            version_string = version
        else:
            version_string = u""

        return cls(name=name, version_string=version_string,
                   build_number=build_number)

    @classmethod
    def from_spec_string(cls, s):
        """
        Create a Requirement from a spec string (as used in
        EGG-INFO/spec/depend).
        """
        s = _translate_invalid_requirement(s)
        parts = s.split()
        if len(parts) == 1:
            name = parts[0]
            if "-" in name:
                raise InvalidRequirementString(name)
            return cls(name=name)
        elif len(parts) == 2:
            name, version = parts
            parts = version.split("-")
            if len(parts) == 2:
                upstream, build_number = parts
                build_number = int(build_number)
            else:
                upstream, build_number = version, -1
            return cls(name=name, version_string=upstream,
                       build_number=build_number)
        else:
            raise InvalidRequirementString(name)

    def __str__(self):
        if len(self.version_string) > 0:
            if self.build_number > 0:
                return u"{0} {1}-{2}".format(
                    self.name, self.version_string, self.build_number
                )
            else:
                return u"{0} {1}".format(self.name, self.version_string)
        else:
            return self.name


_METADATA_TEMPLATES = {
    M("1.1"): """\
metadata_version = '1.1'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}
packages = {packages}
""",
    M("1.2"): """\
metadata_version = '1.2'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}
python_tag = {python_tag!r}
packages = {packages}
""",
    M("1.3"): """\
metadata_version = '1.3'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}

python_tag = {python_tag!r}
abi_tag = {abi_tag!r}
platform_tag = {platform_tag!r}

packages = {packages}
""",
    M("1.4"): """\
metadata_version = '1.4'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}

python_tag = {python_tag!r}
abi_tag = {abi_tag!r}
platform_tag = {platform_tag!r}

platform_abi = {platform_abi!r}

packages = {packages}
"""
}


_METADATA_DEFAULT_VERSION_STRING = "1.4"
_METADATA_DEFAULT_VERSION = M(_METADATA_DEFAULT_VERSION_STRING)


def _epd_platform_from_raw_spec(raw_spec):
    """ Create an EPDPlatform instance from the metadata info returned by
    parse_rawspec.

    if no platform is defined ('platform' and 'osdist' set to None), then
    None is returned.
    """
    arch_string = raw_spec[_TAG_ARCH]
    platform_string = raw_spec[_TAG_PLATFORM]
    osdist_string = raw_spec[_TAG_OSDIST]
    if platform_string is None and osdist_string is None:
        return None
    else:
        return EPDPlatform._from_spec_depend_data(
            platform_string, osdist_string, arch_string
        )


@attributes
class LegacySpecDepend(object):
    """
    This models the EGG-INFO/spec/depend content.
    """
    # Name is taken from egg path, so may be upper case
    name = text_attr()
    """
    Egg name
    """

    version = text_attr()
    """
    Upstream version (as a string).
    """

    build = attr(validator=instance_of(int))
    """
    Build number
    """

    python = text_or_none_attr()
    """
    Python version
    """

    python_tag = text_or_none_attr()
    """
    Python tag (as defined in PEP 425).
    """

    abi_tag = text_or_none_attr()
    """
    ABI tag (as defined in PEP 425), except that 'none' is None.
    """

    platform_tag = text_or_none_attr()
    """
    Platform tag (as defined in PEP 425), except that 'any' is None.
    """

    platform_abi = text_or_none_attr()
    """
    Platform abi. None if no abi.
    """

    packages = attr(validator=instance_of(list))
    """
    List of dependencies for this egg
    """

    _epd_legacy_platform = attr(
        validator=optional(instance_of(LegacyEPDPlatform))
    )

    _metadata_version = attr(validator=instance_of(MetadataVersion))

    @classmethod
    def _from_data(cls, data, epd_platform):
        args = data.copy()
        args[_TAG_METADATA_VERSION] = M(
            args.get(_TAG_METADATA_VERSION, _METADATA_DEFAULT_VERSION_STRING)
        )

        if epd_platform is None:
            _epd_legacy_platform = None
        else:
            _epd_legacy_platform = LegacyEPDPlatform(epd_platform)
        args["_epd_legacy_platform"] = _epd_legacy_platform

        args[_TAG_PACKAGES] = [
            Requirement.from_spec_string(s)
            for s in args.get(_TAG_PACKAGES, [])
        ]

        return cls(
            args["name"],
            args["version"],
            args["build"],
            args["python"],
            args["python_tag"],
            args["abi_tag"],
            args["platform_tag"],
            args["platform_abi"],
            args["packages"],
            args["_epd_legacy_platform"],
            args["metadata_version"],
        )

    @classmethod
    def from_egg(cls, path_or_file):
        sha256 = None
        if isinstance(path_or_file, string_types):
            if (
                may_be_in_platform_blacklist(path_or_file)
                or may_be_in_python_tag_blacklist(path_or_file)
            ):
                sha256 = compute_sha256(path_or_file)
        else:
            with _keep_position(path_or_file.fp):
                sha256 = compute_sha256(path_or_file.fp)
        return cls._from_egg(path_or_file, sha256)

    @classmethod
    def _from_egg(cls, path_or_file, sha256):
        def _create_spec_depend(zp):
            epd_platform_string = EGG_PLATFORM_BLACK_LIST.get(sha256)
            if epd_platform_string is None:
                epd_platform = None
            else:
                epd_platform = EPDPlatform.from_epd_string(epd_platform_string)

            try:
                spec_depend_string = zp.read(_SPEC_DEPEND_LOCATION).decode()
            except KeyError:
                msg = ("File {0!r} is not an Enthought egg (is missing {1})"
                       .format(path_or_file, _SPEC_DEPEND_LOCATION))
                raise MissingMetadata(msg)
            else:
                data, epd_platform = _normalized_info_from_string(
                    spec_depend_string, epd_platform, sha256
                )
                return cls._from_data(data, epd_platform)

        if isinstance(path_or_file, string_types):
            with zipfile2.ZipFile(path_or_file) as zp:
                return _create_spec_depend(zp)
        else:
            return _create_spec_depend(path_or_file)

    @classmethod
    def from_string(cls, spec_depend_string):
        data, epd_platform = _normalized_info_from_string(spec_depend_string)
        return cls._from_data(data, epd_platform)

    @property
    def arch(self):
        """
        Egg architecture.
        """
        if self._epd_legacy_platform is None:
            return None
        else:
            return self._epd_legacy_platform.arch._legacy_name

    @property
    def egg_name(self):
        """
        Full egg name (including .egg extension).
        """
        return egg_name(self.name, self.version, self.build)

    @property
    def osdist(self):
        if self._epd_legacy_platform is None:
            return None
        else:
            return self._epd_legacy_platform.osdist

    @property
    def platform(self):
        """
        The legacy platform name (sys.platform).
        """
        if self._epd_legacy_platform is None:
            return None
        else:
            return self._epd_legacy_platform.platform

    @property
    def metadata_version(self):
        return self._metadata_version

    @metadata_version.setter
    def metadata_version(self, value):
        self._metadata_version = value

    def _to_dict(self):
        raw_data = {
            _TAG_NAME: self.name,
            _TAG_VERSION: self.version,
            _TAG_BUILD: self.build,
            _TAG_ARCH: self.arch,
            _TAG_PLATFORM: self.platform,
            _TAG_OSDIST: self.osdist,
            _TAG_PACKAGES: [str(p) for p in self.packages],
            _TAG_PYTHON: self.python,
            _TAG_PYTHON_PEP425_TAG: self.python_tag,
            _TAG_ABI_PEP425_TAG: self.abi_tag,
            _TAG_PLATFORM_PEP425_TAG: self.platform_tag,
            _TAG_PLATFORM_ABI: self.platform_abi,
            _TAG_METADATA_VERSION: self.metadata_version
        }

        return raw_data

    def to_string(self):
        """
        Returns a string that is suitable for the depend file inside our
        legacy egg.
        """
        template = _METADATA_TEMPLATES.get(self.metadata_version, None)
        data = self._to_dict()

        if six.PY2:
            # Hack to avoid the 'u' prefix to appear in the spec/depend entries
            for k, v in data.items():
                data[k] = encode_if_needed(v)

        # This is just to ensure the exact same string as the produced by the
        # legacy buildsystem
        if len(self.packages) == 0:
            data[_TAG_PACKAGES] = "[]"
        else:
            if six.PY2:
                packages = [decode_if_needed(p) for p in self.packages]
            else:
                packages = self.packages
            data[_TAG_PACKAGES] = (
                u"[\n{0}\n]".format(
                    "\n".join("  '{0}',".format(p) for p in packages)
                )
            )
        return template.format(**data)


class Dependencies(object):
    """ Object storing the various dependencies for an egg.

    Each attribute is a tuple of Requirement instances.
    """
    def __init__(self, runtime=None, build=None):
        self.runtime = runtime or ()
        self.build = runtime or ()


def _metadata_version_to_tuple(metadata_version):
    """ Convert a metadata version string to a tuple for comparison."""
    return tuple(int(s) for s in metadata_version.split("."))


def _normalized_info_from_string(spec_depend_string, epd_platform=None,
                                 sha256=None):
    """ Return a 'normalized' dictionary from the given spec/depend string.

    Note: the name value is NOT lower-cased, so that the egg filename may
    rebuilt from the data.
    """
    raw_data = parse_rawspec(spec_depend_string)

    data = {}
    for k in (_TAG_METADATA_VERSION,
              _TAG_NAME, _TAG_VERSION, _TAG_BUILD,
              _TAG_ARCH, _TAG_OSDIST, _TAG_PLATFORM,
              _TAG_PYTHON, _TAG_PACKAGES):
        data[k] = raw_data[k]

    epd_platform = epd_platform or _epd_platform_from_raw_spec(data)
    for k in (_TAG_ARCH, _TAG_PLATFORM, _TAG_OSDIST):
        data.pop(k)

    metadata_version = MetadataVersion.from_string(data[_TAG_METADATA_VERSION])

    python_tag = EGG_PYTHON_TAG_BLACK_LIST.get(sha256)
    if python_tag:
        data[_TAG_PYTHON_PEP425_TAG] = python_tag
    else:
        if metadata_version < M("1.2"):
            data[_TAG_PYTHON_PEP425_TAG] = _guess_python_tag(
                raw_data[_TAG_PYTHON]
            )
        else:
            data[_TAG_PYTHON_PEP425_TAG] = raw_data[_TAG_PYTHON_PEP425_TAG]

    if metadata_version < M("1.3"):
        python_tag = data[_TAG_PYTHON_PEP425_TAG]
        data[_TAG_ABI_PEP425_TAG] = _guess_abi_tag(epd_platform, python_tag)
        data[_TAG_PLATFORM_PEP425_TAG] = _guess_platform_tag(epd_platform)
    else:
        data[_TAG_ABI_PEP425_TAG] = raw_data[_TAG_ABI_PEP425_TAG]
        data[_TAG_PLATFORM_PEP425_TAG] = raw_data[_TAG_PLATFORM_PEP425_TAG]

    if metadata_version < M("1.4"):
        python_tag = data[_TAG_PYTHON_PEP425_TAG]
        platform_abi = _guess_platform_abi(epd_platform, python_tag)
    else:
        platform_abi = raw_data[_TAG_PLATFORM_ABI]
    data[_TAG_PLATFORM_ABI] = platform_abi

    return data, epd_platform


_JSON_METADATA_VERSION = "metadata_version"
_JSON__RAW_NAME = "_raw_name"
_JSON_VERSION = "version"
_JSON_EPD_PLATFORM = "epd_platform"
_JSON_PYTHON_TAG = "python_tag"
_JSON_ABI_TAG = "abi_tag"
_JSON_PLATFORM_TAG = "platform_tag"
_JSON_PLATFORM_ABI_TAG = "platform_abi_tag"
_JSON_RUNTIME_DEPENDENCIES = "runtime_dependencies"
_JSON_SUMMARY = "summary"


[docs]class EggMetadata(object): """ Enthought egg metadata for format 1.x. """ HIGHEST_SUPPORTED_METADATA_VERSION = _METADATA_DEFAULT_VERSION """ Highest supported metadata version (as a MetadataVersion object). If the parsed metadata is higher, it will not be possible to write back the metadata. If the parsed metadata version is not compatible (different major version), then parsing will raise an UnsupportedMetadata exception as well. """ @staticmethod def _may_be_in_blacklist(path): return ( may_be_in_platform_blacklist(path) or may_be_in_pkg_info_blacklist(path) or may_be_in_python_tag_blacklist(path) )
[docs] @classmethod def from_egg(cls, path_or_file, strict=True): """ Create a EggMetadata instance from an existing Enthought egg. Parameters ---------- path: str or file-like object. If a string, understood as the path to the egg. Otherwise, understood as a zipfile-like object. strict: bool If True, will fail if metadata cannot be decoded correctly (e.g. unicode errors in EGG-INFO/PKG-INFO). If false, will ignore those errors, at the risk of data loss. """ sha256 = None if isinstance(path_or_file, string_types): if cls._may_be_in_blacklist(path_or_file): sha256 = compute_sha256(path_or_file) else: with _keep_position(path_or_file.fp): sha256 = compute_sha256(path_or_file.fp) return cls._from_egg(path_or_file, sha256, strict)
@classmethod def from_json_dict(cls, json_dict, pkg_info): version = EnpkgVersion.from_string(json_dict[_JSON_VERSION]) if json_dict[_JSON_PYTHON_TAG] is not None: python = PythonImplementation.from_string(json_dict[_JSON_PYTHON_TAG]) else: python = None if json_dict[_JSON_EPD_PLATFORM] is None: epd_platform = None else: epd_platform = EPDPlatform.from_epd_string(json_dict[_JSON_EPD_PLATFORM]) dependencies = Dependencies(tuple(json_dict[_JSON_RUNTIME_DEPENDENCIES])) metadata_version = MetadataVersion.from_string( json_dict[_JSON_METADATA_VERSION] ) return cls( json_dict[_JSON__RAW_NAME], version, epd_platform, python, json_dict[_JSON_ABI_TAG], json_dict[_JSON_PLATFORM_ABI_TAG], dependencies, pkg_info, json_dict[_JSON_SUMMARY], metadata_version=metadata_version ) @classmethod def _from_egg(cls, path_or_file, sha256, strict=True): def _read_summary(fp): summary_arcname = "EGG-INFO/spec/summary" try: summary = fp.read(summary_arcname) except KeyError: # the summary file may not exist for eggs built with # endist/repack summary = b"" return summary.decode("utf8") def _compute_all_metadata(fp): summary = _read_summary(fp) pkg_info_data = _read_pkg_info(fp) if pkg_info_data is None: pkg_info_string = None else: pkg_info_string = _convert_if_needed( pkg_info_data, sha256, strict ) spec_depend = LegacySpecDepend._from_egg(fp, sha256) return summary, pkg_info_string, spec_depend if isinstance(path_or_file, string_types): with zipfile2.ZipFile(path_or_file) as zp: summary, pkg_info_string, spec_depend = _compute_all_metadata(zp) else: summary, pkg_info_string, spec_depend = _compute_all_metadata( path_or_file ) return cls._from_spec_depend(spec_depend, pkg_info_string, summary) @classmethod def _from_spec_depend(cls, spec_depend, pkg_info, summary, metadata_version=None): raw_name = spec_depend.name version = EnpkgVersion.from_upstream_and_build(spec_depend.version, spec_depend.build) python_tag = spec_depend.python_tag abi_tag = spec_depend.abi_tag platform_abi = spec_depend.platform_abi if spec_depend._epd_legacy_platform is None: platform = None else: platform_string = str(spec_depend._epd_legacy_platform) platform = EPDPlatform.from_epd_string(platform_string) dependencies = Dependencies( tuple(dep for dep in spec_depend.packages) ) metadata_version = metadata_version or spec_depend.metadata_version return cls(raw_name, version, platform, python_tag, abi_tag, platform_abi, dependencies, pkg_info, summary, metadata_version)
[docs] @classmethod def from_egg_metadata(cls, egg_metadata, **kw): """ Utility ctor to create a new EggMetadata instance from an existing one, potentially updating some metadata. Any keyword argument (except `egg_metadata`) is understood as an argument to EggMetadata.__init__. Parameters ---------- egg_metadata: EggMetadata """ passed_kw = {"raw_name": egg_metadata._raw_name} for k in ( "version", "platform", "python", "abi_tag", "pkg_info", "summary", "metadata_version", "platform_abi", ): passed_kw[k] = getattr(egg_metadata, k) passed_kw["dependencies"] = Dependencies( egg_metadata.runtime_dependencies ) passed_kw.update(**kw) return cls(**passed_kw)
def __init__(self, raw_name, version, platform, python, abi_tag, platform_abi, dependencies, pkg_info, summary, metadata_version=None): """ EggMetadata instances encompass Enthought egg metadata. Note: the constructor is considered private, please use one of the from_* class methods. Parameters ---------- raw_name: str The 'raw' name, i.e. the name value in spec/depend. version: EnpkgVersion The full version platform: EPDPlatform An EPDPlatform instance, or None for cross-platform eggs python: Python The python implementation abi_tag: str The ABI tag, e.g. 'cp27m'. May be None. platform_abi: str The platform abi, e.g. 'msvc2008', 'gnu', etc. May be None. dependencies: Dependencies A Dependencies instance. pkg_info: PackageInfo or str or None Instance modeling the PKG-INFO content of the egg. If a string is passed, it is assumed to be the PKG-INFO content, and is lazily parsed into a PackageInfo when pkg_info is accessed for the first time. summary: str The summary. Models the string in EGG-INFO/spec/summary. May be empty. """ self._raw_name = raw_name self.version = version """ The version, as an EnpkgVersion instance.""" self.platform = platform """ The platform, as a Platform instance.""" if isinstance(python, string_types): python = PythonImplementation.from_string(python) self.python = python """ The python implementation.""" if abi_tag is not None and isinstance(abi_tag, six.string_types): abi_tag = PythonABI(abi_tag) self.abi = abi_tag """ The ABI tag, following the PEP425 format, except that no ABI is sorted as None.""" if ( platform_abi is not None and isinstance(platform_abi, six.string_types) ): platform_abi = PlatformABI(platform_abi) self.platform_abi = platform_abi self.runtime_dependencies = tuple(dependencies.runtime) """ List of runtime dependencies (as strings).""" self.metadata_version = metadata_version or _METADATA_DEFAULT_VERSION """ The version format of the underlying metadata.""" self._pkg_info = pkg_info """ A PackageInfo instance modeling the underlying PKG-INFO. May be None for eggs without an PKG-INFO file.""" self.summary = summary """ The summary string.""" @property def abi_tag(self): if self.abi is None: return None else: return self.abi.pep425_tag @property def abi_tag_string(self): return PythonABI.pep425_tag_string(self.abi) @property def build(self): """ The build number.""" return self.version.build @property def egg_basename(self): """ The egg "base name", i.e. the name part of the egg filename.""" return self._raw_name @property def egg_name(self): """ The egg filename.""" return self._spec_depend.egg_name @property def is_strictly_supported(self): """ Returns True if the given metadata_version is fully supported. A metadata_version is fully supported iff: - metadata_version.major == EggMetadata.HIGHEST_SUPPORTED_METADATA_VERSION.major - and metadata_version.minor <= EggMetadata.HIGHEST_SUPPORTED_METADATA_VERSION.minor """ max_supported = EggMetadata.HIGHEST_SUPPORTED_METADATA_VERSION return ( _are_compatible(self.metadata_version, max_supported) and self.metadata_version.minor <= max_supported.minor ) @property def kind(self): return "egg" @property def name(self): """ The package name.""" return self._raw_name.lower().replace("-", "_") @property def pkg_info(self): if isinstance(self._pkg_info, six.string_types): self._pkg_info = PackageInfo.from_string(self._pkg_info) return self._pkg_info @property def platform_abi_tag(self): if self.platform_abi is None: return None else: return self.platform_abi.pep425_tag @property def platform_abi_tag_string(self): return PlatformABI.pep425_tag_string(self.platform_abi) @property def platform_tag(self): """ Platform tag following PEP425, except that no platform is represented as None and not 'any'.""" if self.platform is None: return None else: return self.platform.pep425_tag @property def platform_tag_string(self): return EPDPlatform.pep425_tag_string(self.platform) @property def python_tag(self): if self.python is None: return None else: return self.python.pep425_tag @property def python_tag_string(self): return PythonImplementation.pep425_tag_string(self.python) @property def spec_depend_string(self): return self._spec_depend.to_string() @property def upstream_version(self): return six.text_type(self.version.upstream) @property def _python(self): if self.python is None: return None else: return u"{0}.{1}".format(self.python.major, self.python.minor) @property def _spec_depend(self): if not self.is_strictly_supported: msg = "Cannot write back metadata with unsupported version {0!r}" raise UnsupportedMetadata( self.metadata_version, msg.format(str(self.metadata_version)) ) if self.platform is None: epd_platform = None else: legacy_epd_platform = LegacyEPDPlatform(self.platform) epd_platform = legacy_epd_platform._epd_platform args = { "name": self._raw_name, "version": self.upstream_version, "build": self.build, "python": self._python, "python_tag": self.python_tag, "abi_tag": self.abi_tag, "platform_tag": self.platform_tag, "platform_abi": self.platform_abi_tag, "packages": [six.text_type(p) for p in self.runtime_dependencies], "metadata_version": six.text_type(self.metadata_version), } return LegacySpecDepend._from_data(args, epd_platform) # Public methods
[docs] def dump(self, path): """ Write the metadata to the given path as a metadata egg. A metadata egg is a zipfile using the same structured as an egg, except that it only contains metadata. Parameters ---------- path : str The path to write the zipped metadata into. """ with zipfile2.ZipFile(path, "w", zipfile2.ZIP_DEFLATED) as zp: zp.writestr( _SPEC_DEPEND_LOCATION, self.spec_depend_string.encode() ) zp.writestr( _SPEC_SUMMARY_LOCATION, self.summary.encode() ) if self.pkg_info: self.pkg_info._dump_as_zip(zp)
def to_json_dict(self): if self.platform is None: epd_platform = None else: epd_platform = six.text_type(self.platform) return { _JSON_METADATA_VERSION: six.text_type(self.metadata_version), _JSON__RAW_NAME: self._raw_name, _JSON_VERSION: six.text_type(self.version), _JSON_EPD_PLATFORM: epd_platform, _JSON_PYTHON_TAG: self.python_tag, _JSON_ABI_TAG: self.abi_tag, _JSON_PLATFORM_TAG: self.platform_tag, _JSON_PLATFORM_ABI_TAG: self.platform_abi_tag, _JSON_RUNTIME_DEPENDENCIES: [ six.text_type(p) for p in self.runtime_dependencies ], _JSON_SUMMARY: self.summary, } # Protocol implementations def __eq__(self, other): if isinstance(other, self.__class__): return ( self.spec_depend_string == other.spec_depend_string and self.summary == other.summary and self.pkg_info == other.pkg_info ) else: raise TypeError( "Only equality between EggMetadata instances is supported" ) def __ne__(self, other): return not self == other