Source code for npe2.manifest._package_metadata
from importlib.metadata import metadata
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Union
from pydantic import BaseModel, Extra, Field, constr, root_validator
from pydantic.fields import SHAPE_LIST
if TYPE_CHECKING:
import email.message
# https://packaging.python.org/specifications/core-metadata/
MetadataVersion = Literal["1.0", "1.1", "1.2", "2.0", "2.1", "2.2"]
_alphanum = "[a-zA-Z0-9]"
PackageName = constr(regex=f"^{_alphanum}[a-zA-Z0-9._-]*{_alphanum}$")
[docs]class PackageMetadata(BaseModel):
"""Pydantic model for standard python package metadata.
https://www.python.org/dev/peps/pep-0566/
https://packaging.python.org/specifications/core-metadata/
The `importlib.metadata` provides the `metadata()` function,
but it returns a somewhat awkward `email.message.Message` object.
"""
class Config:
extra = Extra.ignore
metadata_version: MetadataVersion = Field(
"1.0", description="Version of the file format"
)
name: PackageName = Field( # type: ignore
...,
description="The name of the distribution. The name field "
"is the primary identifier for a distribution.",
)
# technically there is PackageVersion regex at
# https://www.python.org/dev/peps/pep-0440/#id81
# but it will fail on some dev versions, and it's not worth it.
version: str = Field(
...,
description="A string containing the distribution's version number. "
"This field must be in the format specified in PEP 440.",
)
dynamic: Optional[List[str]] = Field(
None,
description="A string containing the name of another core metadata "
"field. The field names Name and Version may not be specified in this field.",
min_ver="2.2",
)
platform: Optional[List[str]] = Field(
None,
description="A Platform specification describing an operating system "
"supported by the distribution which is not listed in the “Operating System” "
"Trove classifiers. See “Classifier” below.",
)
supported_platform: Optional[List[str]] = Field(
None,
description="Binary distributions containing a PKG-INFO file will use the "
"Supported-Platform field in their metadata to specify the OS and CPU for "
"which the binary distribution was compiled",
min_ver="1.1",
)
summary: Optional[str] = Field(
None, description="A one-line summary of what the distribution does."
)
description: Optional[str] = Field(
None,
description="A longer description of the distribution that can "
"run to several paragraphs.",
)
description_content_type: Optional[str] = Field(
None,
description="A string stating the markup syntax (if any) used in the "
"distribution's description, so that tools can intelligently render the "
"description. The type/subtype part has only a few legal values: "
"text/plain, text/x-rst, text/markdown",
min_ver="2.1",
)
keywords: Optional[str] = Field(
None,
description="A list of additional keywords, separated by commas, to be used "
"to assist searching for the distribution in a larger catalog.",
)
home_page: Optional[str] = Field(
None,
description="A string containing the URL for the distribution's home page.",
)
download_url: Optional[str] = Field(
None,
description="A string containing the URL from which THIS version of the "
"distribution can be downloaded.",
min_ver="1.1",
)
author: Optional[str] = Field(
None,
description="A string containing the author's name at a minimum; "
"additional contact information may be provided.",
)
author_email: Optional[str] = Field(
None,
description="A string containing the author's e-mail address. It can contain "
"a name and e-mail address in the legal forms for a RFC-822 From: header.",
)
maintainer: Optional[str] = Field(
None,
description="A string containing the maintainer's name at a minimum; "
"additional contact information may be provided.",
min_ver="1.2",
)
maintainer_email: Optional[str] = Field(
None,
description="A string containing the maintainer's e-mail address. It can "
"contain a name and e-mail address in the legal forms for a "
"RFC-822 From: header.",
min_ver="1.2",
)
license: Optional[str] = Field(
None,
description="Text indicating the license covering the distribution where the "
"license is not a selection from the “License” Trove classifiers. See "
"“Classifier” below. This field may also be used to specify a particular "
"version of a license which is named via the Classifier field, or to "
"indicate a variation or exception to such a license.",
)
classifier: Optional[List[str]] = Field(
None,
description="Each entry is a string giving a single classification value for "
"the distribution. Classifiers are described in PEP 301, and the Python "
"Package Index publishes a dynamic list of currently defined classifiers.",
min_ver="1.1",
)
requires_dist: Optional[List[str]] = Field(
None,
description="The field format specification was relaxed to accept the syntax "
"used by popular publishing tools. Each entry contains a string naming some "
"other distutils project required by this distribution.",
min_ver="1.2",
)
requires_python: Optional[str] = Field(
None,
description="This field specifies the Python version(s) that the distribution "
"is guaranteed to be compatible with. Installation tools may look at this "
"when picking which version of a project to install. "
"The value must be in the format specified in Version specifiers (PEP 440).",
min_ver="1.2",
)
requires_external: Optional[List[str]] = Field(
None,
description="The field format specification was relaxed to accept the syntax "
"used by popular publishing tools. Each entry contains a string describing "
"some dependency in the system that the distribution is to be used. This "
"field is intended to serve as a hint to downstream project maintainers, and "
"has no semantics which are meaningful to the distutils distribution.",
min_ver="1.2",
)
project_url: Optional[List[str]] = Field(
None,
description="A string containing a browsable URL for the project and a label "
"for it, separated by a comma.",
min_ver="1.2",
)
provides_extra: Optional[List[str]] = Field(
None,
description="A string containing the name of an optional feature. Must be a "
"valid Python identifier. May be used to make a dependency conditional on "
"whether the optional feature has been requested.",
min_ver="2.1",
)
# rarely_used
provides_dist: Optional[List[str]] = Field(None, min_ver="1.2")
obsoletes_dist: Optional[List[str]] = Field(None, min_ver="1.2")
@root_validator(pre=True)
def _validate_root(cls, values):
if "metadata_version" not in values:
fields = cls.__fields__
mins = {fields[n].field_info.extra.get("min_ver", "1.0") for n in values}
values["metadata_version"] = str(max(float(x) for x in mins))
return values
[docs] @classmethod
def for_package(cls, name: str) -> "PackageMetadata":
"""Get PackageMetadata from a package name."""
return cls.from_dist_metadata(metadata(name))
[docs] @classmethod
def from_dist_metadata(cls, meta: "email.message.Message") -> "PackageMetadata":
"""Accepts importlib.metadata.Distribution.metadata"""
manys = [f.name for f in cls.__fields__.values() if f.shape == SHAPE_LIST]
d: Dict[str, Union[str, List[str]]] = {}
for key, value in meta.items():
key = _norm(key)
if key in manys:
d.setdefault(key, []).append(value) # type: ignore
else:
d[key] = value
return cls.parse_obj(d)
def __hash__(self) -> int:
return id(self)
def _norm(string: str) -> str:
return string.replace("-", "_").replace(" ", "_").lower()