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