From 896c88b8d5b1d78497aa58d7bde9246db7df5932 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 7 Mar 2022 19:10:15 -0800 Subject: [PATCH 1/9] A bare-bones `metadata` module --- packaging/metadata.py | 172 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 packaging/metadata.py diff --git a/packaging/metadata.py b/packaging/metadata.py new file mode 100644 index 00000000..fc1999c0 --- /dev/null +++ b/packaging/metadata.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +import enum +import typing + +from . import specifiers +from . import utils + +if typing.TYPE_CHECKING: + from collections.abc import Iterable + from typing import List, Optional, Set, Tuple + + from . import requirements + from . import version as packaging_version # Alt name avoids shadowing. + + +class InvalidMetadata(ValueError): + """ + Invalid metadata found. + """ + + +@enum.unique +class MetadataVersion(enum.Enum): + + """ + Core metadata versions. + """ + + V1_0 = "1.0" + V1_1 = "1.1" + V1_2 = "1.2" + V2_1 = "2.1" + V2_2 = "2.2" + + +@enum.unique +class DynamicField(enum.Enum): + + """ + Field names for the `dynamic` field. + + All values are lower-cased for easy comparison. + """ + + # `Name`, `Version`, and `Metadata-Version` are invalid in `Dynamic`. + # 1.0 + PLATFORM = "platform" + SUMMARY = "summary" + DESCRIPTION = "description" + KEYWORDS = "keywords" + HOME_PAGE = "home-page" + AUTHOR = "author" + AUTHOR_EMAIL = "author-email" + LICENSE = "license" + # 1.1 + SUPPORTED_PLATFORM = "supported-platform" + DOWNLOAD_URL = "download-url" + CLASSIFIER = "classifier" + # 1.2 + MAINTAINER = "maintainer" + MAINTAINER_EMAIL = "maintainer-email" + REQUIRES_DIST = "requires-dist" + REQUIRES_PYTHON = "requires-python" + REQUIRES_EXTERNAL = "requires-external" + PROJECT_URL = "project-url" + PROVIDES_DIST = "provides-dist" + OBSOLETES_DIST = "obsoletes-dist" + # 2.1 + DESCRIPTION_CONTENT_TYPE = "description-content-type" + PROVIDES_EXTRA = "provides-extra" + + +# Type aliases. +_NameAndEmail = Tuple[Optional[str], str] +_LabelAndURL = Tuple[str, str] + + +class Metadata: + + """ + A representation of core metadata. + """ + + name: utils.NormalizedName + version: packaging_version.Version + platforms: Set[str] + summary: str + description: str + keywords: List[str] + home_page: str + author: str + author_emails: List[_NameAndEmail] + license: str + supported_platforms: Set[str] + download_url: str + classifiers: Set[str] + maintainer: str + maintainer_emails: List[_NameAndEmail] + requires_dists: Set[requirements.Requirement] + requires_python: specifiers.SpecifierSet + requires_externals: Set[str] + project_urls: Set[_LabelAndURL] + provides_dists: Set[requirements.Requirement] + obsoletes_dists: Set[requirements.Requirement] + description_content_type: str + provides_extras: Set[str] + dynamic: Set[DynamicField] + + def __init__( + self, + name: str, + version: packaging_version.Version, + *, + # 1.0 + platforms: Optional[Iterable[str]] = None, + summary: Optional[str] = None, + description: Optional[str] = None, + keywords: Optional[Iterable[str]] = None, + home_page: Optional[str] = None, + author: Optional[str] = None, + author_emails: Optional[Iterable[_NameAndEmail]] = None, + license: Optional[str] = None, + # 1.1 + supported_platforms: Optional[Iterable[str]] = None, + download_url: Optional[str] = None, + classifiers: Optional[Iterable[str]] = None, # TODO: OK? + # 1.2 + maintainer: Optional[str] = None, + maintainer_emails: Optional[Iterable[_NameAndEmail]] = None, + requires_dists: Optional[Iterable[requirements.Requirement]] = None, + requires_python: Optional[specifiers.SpecifierSet] = None, + requires_externals: Optional[Iterable[str]] = None, # TODO: OK? + project_urls: Optional[Iterable[_LabelAndURL]] = None, + provides_dists: Optional[Iterable[requirements.Requirement]] = None, + obsoletes_dists: Optional[Iterable[requirements.Requirement]] = None, + # 2.1 + description_content_type: Optional[str] = None, # TODO: OK? + provides_extras: Optional[Iterable[str]] = None, # TODO: OK? + # 2.2 + dynamic: Optional[Iterable[DynamicField]] = None, + ) -> None: + """ + Set all attributes on the instance. + + An argument of `None` will be converted to an appropriate, false-y value + (e.g. the empty string). + """ + self.name = utils.canonicalize_name(name) + self.version = version + self.platforms = set(platforms or []) + self.summary = summary or "" + self.description = description or "" + self.keywords = list(keywords or []) + self.home_page = home_page or "" + self.author = author or "" + self.author_emails = list(author_emails or []) + self.license = license or "" + self.supported_platforms = set(supported_platforms or []) + self.download_url = download_url or "" + self.classifiers = set(classifiers or []) + self.maintainer = maintainer or "" + self.maintainer_emails = list(maintainer_emails or []) + self.requires_dists = set(requires_dists or []) + self.requires_python = requires_python or specifiers.SpecifierSet() + self.requires_externals = set(requires_externals or []) + self.project_urls = set(project_urls or []) + self.provides_dists = set(provides_dists or []) + self.obsoletes_dists = set(obsoletes_dists or []) + self.description_content_type = description_content_type or "" + self.provides_extras = set(provides_extras or []) + self.dynamic = set(dynamic or []) From 2619b0c943ced048f8c09ba35b2999f59a669066 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sun, 13 Mar 2022 19:16:57 -0700 Subject: [PATCH 2/9] Add `display_name` and `canonical_name` --- packaging/metadata.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/packaging/metadata.py b/packaging/metadata.py index fc1999c0..680c0086 100644 --- a/packaging/metadata.py +++ b/packaging/metadata.py @@ -82,7 +82,10 @@ class Metadata: A representation of core metadata. """ - name: utils.NormalizedName + # A property named `display_name` exposes the value. + _display_name: str + # A property named `canonical_name` exposes the value. + _canonical_name: utils.NormalizedName version: packaging_version.Version platforms: Set[str] summary: str @@ -146,7 +149,7 @@ def __init__( An argument of `None` will be converted to an appropriate, false-y value (e.g. the empty string). """ - self.name = utils.canonicalize_name(name) + self.display_name = name self.version = version self.platforms = set(platforms or []) self.summary = summary or "" @@ -170,3 +173,19 @@ def __init__( self.description_content_type = description_content_type or "" self.provides_extras = set(provides_extras or []) self.dynamic = set(dynamic or []) + + @property + def display_name(self) -> str: + return self._display_name + + @display_name.setter + def display_name(self, value, /) -> None: + """""" + self._display_name = value + self._canonical_name = utils.canonicalize_name(value) + + # Use functools.cached_property once Python 3.7 support is dropped. + # Value is set by self.display_name.setter to keep in sync with self.display_name. + @property + def canonical_name(self) -> utils.NormalizedName: + return self._canonical_name From 0c35b52047ce3b6a77c711b3c36c73278cd65faa Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 1 Apr 2022 17:14:38 -0700 Subject: [PATCH 3/9] Update for PEP 685 --- packaging/metadata.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packaging/metadata.py b/packaging/metadata.py index 680c0086..acdac566 100644 --- a/packaging/metadata.py +++ b/packaging/metadata.py @@ -32,6 +32,7 @@ class MetadataVersion(enum.Enum): V1_2 = "1.2" V2_1 = "2.1" V2_2 = "2.2" + v2_3 = "2.3" @enum.unique @@ -107,7 +108,7 @@ class Metadata: provides_dists: Set[requirements.Requirement] obsoletes_dists: Set[requirements.Requirement] description_content_type: str - provides_extras: Set[str] + provides_extras: Set[utils.NormalizedName] dynamic: Set[DynamicField] def __init__( @@ -139,7 +140,7 @@ def __init__( obsoletes_dists: Optional[Iterable[requirements.Requirement]] = None, # 2.1 description_content_type: Optional[str] = None, # TODO: OK? - provides_extras: Optional[Iterable[str]] = None, # TODO: OK? + provides_extras: Optional[Iterable[utils.NormalizedName]] = None, # 2.2 dynamic: Optional[Iterable[DynamicField]] = None, ) -> None: @@ -180,7 +181,7 @@ def display_name(self) -> str: @display_name.setter def display_name(self, value, /) -> None: - """""" + """Set the value for self.display_name and self.canonical_name.""" self._display_name = value self._canonical_name = utils.canonicalize_name(value) From f80b8d99e343a4c65e1fe98a7ebf206c6d18974e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 2 Apr 2022 12:19:01 -0700 Subject: [PATCH 4/9] Add tests for name normalization --- packaging/metadata.py | 9 ++++----- tests/test_metadata.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 tests/test_metadata.py diff --git a/packaging/metadata.py b/packaging/metadata.py index acdac566..c5e48ba8 100644 --- a/packaging/metadata.py +++ b/packaging/metadata.py @@ -13,6 +13,10 @@ from . import requirements from . import version as packaging_version # Alt name avoids shadowing. + # Type aliases. + _NameAndEmail = Tuple[Optional[str], str] + _LabelAndURL = Tuple[str, str] + class InvalidMetadata(ValueError): """ @@ -72,11 +76,6 @@ class DynamicField(enum.Enum): PROVIDES_EXTRA = "provides-extra" -# Type aliases. -_NameAndEmail = Tuple[Optional[str], str] -_LabelAndURL = Tuple[str, str] - - class Metadata: """ diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 00000000..41be4695 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,33 @@ +import pytest + +from packaging import metadata, utils, version + + +class TestNameNormalization: + + version = version.Version("1.0.0") + display_name = "A--B" + canonical_name = utils.canonicalize_name(display_name) + + def test_via_init(self): + metadata_ = metadata.Metadata(self.display_name, self.version) + + assert metadata_.display_name == self.display_name + assert metadata_.canonical_name == self.canonical_name + + def test_via_display_name_setter(self): + metadata_ = metadata.Metadata("a", self.version) + + assert metadata_.display_name == "a" + assert metadata_.canonical_name == "a" + + metadata_.display_name = self.display_name + + assert metadata_.display_name == self.display_name + assert metadata_.canonical_name == self.canonical_name + + def test_no_canonical_name_setter(self): + metadata_ = metadata.Metadata("a", self.version) + + with pytest.raises(AttributeError): + metadata_.canonical_name = "b" # type: ignore From cd8f2e601bbb9d8f6f7cffd4240071e523aeb77a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sun, 8 May 2022 14:59:04 -0700 Subject: [PATCH 5/9] Address some feedback --- packaging/metadata.py | 82 +++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/packaging/metadata.py b/packaging/metadata.py index c5e48ba8..94dccea2 100644 --- a/packaging/metadata.py +++ b/packaging/metadata.py @@ -3,15 +3,16 @@ import enum import typing -from . import specifiers -from . import utils +from . import specifiers, utils if typing.TYPE_CHECKING: from collections.abc import Iterable - from typing import List, Optional, Set, Tuple + from typing import List, Optional, Tuple - from . import requirements - from . import version as packaging_version # Alt name avoids shadowing. + from . import ( # Alt name avoids shadowing. + requirements, + version as packaging_version, + ) # Type aliases. _NameAndEmail = Tuple[Optional[str], str] @@ -31,12 +32,25 @@ class MetadataVersion(enum.Enum): Core metadata versions. """ - V1_0 = "1.0" - V1_1 = "1.1" - V1_2 = "1.2" - V2_1 = "2.1" - V2_2 = "2.2" - v2_3 = "2.3" + # Make sure to update _VERSION_FROM_STR when adding a new version. + V1_0 = 1, 0 + V1_1 = 1, 1 + V1_2 = 1, 2 + V2_1 = 2, 1 + V2_2 = 2, 2 + V2_3 = 2, 3 + + +_VERSION_FROM_STR = { + "1.0": MetadataVersion.V1_0, + "1.1": MetadataVersion.V1_1, + "1.2": MetadataVersion.V1_2, + # While no official 2.0, some projects used it prior to 2.1 being standardized. + "2.0": MetadataVersion.V2_1, + "2.1": MetadataVersion.V2_1, + "2.2": MetadataVersion.V2_2, + "2.3": MetadataVersion.V2_3, +} @enum.unique @@ -87,7 +101,7 @@ class Metadata: # A property named `canonical_name` exposes the value. _canonical_name: utils.NormalizedName version: packaging_version.Version - platforms: Set[str] + platforms: List[str] summary: str description: str keywords: List[str] @@ -95,20 +109,20 @@ class Metadata: author: str author_emails: List[_NameAndEmail] license: str - supported_platforms: Set[str] + supported_platforms: List[str] download_url: str - classifiers: Set[str] + classifiers: List[str] maintainer: str maintainer_emails: List[_NameAndEmail] - requires_dists: Set[requirements.Requirement] + requires_dists: List[requirements.Requirement] requires_python: specifiers.SpecifierSet - requires_externals: Set[str] - project_urls: Set[_LabelAndURL] - provides_dists: Set[requirements.Requirement] - obsoletes_dists: Set[requirements.Requirement] + requires_externals: List[str] + project_urls: List[_LabelAndURL] + provides_dists: List[str] + obsoletes_dists: List[str] description_content_type: str - provides_extras: Set[utils.NormalizedName] - dynamic: Set[DynamicField] + provides_extras: List[utils.NormalizedName] + dynamic_fields: List[DynamicField] def __init__( self, @@ -135,13 +149,13 @@ def __init__( requires_python: Optional[specifiers.SpecifierSet] = None, requires_externals: Optional[Iterable[str]] = None, # TODO: OK? project_urls: Optional[Iterable[_LabelAndURL]] = None, - provides_dists: Optional[Iterable[requirements.Requirement]] = None, - obsoletes_dists: Optional[Iterable[requirements.Requirement]] = None, + provides_dists: Optional[Iterable[str]] = None, + obsoletes_dists: Optional[Iterable[str]] = None, # 2.1 description_content_type: Optional[str] = None, # TODO: OK? provides_extras: Optional[Iterable[utils.NormalizedName]] = None, # 2.2 - dynamic: Optional[Iterable[DynamicField]] = None, + dynamic_fields: Optional[Iterable[DynamicField]] = None, ) -> None: """ Set all attributes on the instance. @@ -151,7 +165,7 @@ def __init__( """ self.display_name = name self.version = version - self.platforms = set(platforms or []) + self.platforms = list(platforms or []) self.summary = summary or "" self.description = description or "" self.keywords = list(keywords or []) @@ -159,20 +173,20 @@ def __init__( self.author = author or "" self.author_emails = list(author_emails or []) self.license = license or "" - self.supported_platforms = set(supported_platforms or []) + self.supported_platforms = list(supported_platforms or []) self.download_url = download_url or "" - self.classifiers = set(classifiers or []) + self.classifiers = list(classifiers or []) self.maintainer = maintainer or "" self.maintainer_emails = list(maintainer_emails or []) - self.requires_dists = set(requires_dists or []) + self.requires_dists = list(requires_dists or []) self.requires_python = requires_python or specifiers.SpecifierSet() - self.requires_externals = set(requires_externals or []) - self.project_urls = set(project_urls or []) - self.provides_dists = set(provides_dists or []) - self.obsoletes_dists = set(obsoletes_dists or []) + self.requires_externals = list(requires_externals or []) + self.project_urls = list(project_urls or []) + self.provides_dists = list(provides_dists or []) + self.obsoletes_dists = list(obsoletes_dists or []) self.description_content_type = description_content_type or "" - self.provides_extras = set(provides_extras or []) - self.dynamic = set(dynamic or []) + self.provides_extras = list(provides_extras or []) + self.dynamic_fields = list(dynamic_fields or []) @property def display_name(self) -> str: From bf99308b7128f1bb95ad479a74018cde102daf96 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sun, 12 Jun 2022 21:28:52 -0700 Subject: [PATCH 6/9] Prep as a data class --- packaging/metadata.py | 129 ++++++++++++++++------------------------- tests/test_metadata.py | 10 ++++ 2 files changed, 60 insertions(+), 79 deletions(-) diff --git a/packaging/metadata.py b/packaging/metadata.py index 94dccea2..9b0bfb0d 100644 --- a/packaging/metadata.py +++ b/packaging/metadata.py @@ -1,22 +1,19 @@ from __future__ import annotations import enum -import typing +from collections.abc import Iterable +from typing import Optional, Tuple -from . import specifiers, utils +from . import ( # Alt name avoids shadowing. + requirements, + specifiers, + utils, + version as packaging_version, +) -if typing.TYPE_CHECKING: - from collections.abc import Iterable - from typing import List, Optional, Tuple - - from . import ( # Alt name avoids shadowing. - requirements, - version as packaging_version, - ) - - # Type aliases. - _NameAndEmail = Tuple[Optional[str], str] - _LabelAndURL = Tuple[str, str] +# Type aliases. +_NameAndEmail = Tuple[Optional[str], str] +_LabelAndURL = Tuple[str, str] class InvalidMetadata(ValueError): @@ -25,34 +22,6 @@ class InvalidMetadata(ValueError): """ -@enum.unique -class MetadataVersion(enum.Enum): - - """ - Core metadata versions. - """ - - # Make sure to update _VERSION_FROM_STR when adding a new version. - V1_0 = 1, 0 - V1_1 = 1, 1 - V1_2 = 1, 2 - V2_1 = 2, 1 - V2_2 = 2, 2 - V2_3 = 2, 3 - - -_VERSION_FROM_STR = { - "1.0": MetadataVersion.V1_0, - "1.1": MetadataVersion.V1_1, - "1.2": MetadataVersion.V1_2, - # While no official 2.0, some projects used it prior to 2.1 being standardized. - "2.0": MetadataVersion.V2_1, - "2.1": MetadataVersion.V2_1, - "2.2": MetadataVersion.V2_2, - "2.3": MetadataVersion.V2_3, -} - - @enum.unique class DynamicField(enum.Enum): @@ -101,28 +70,28 @@ class Metadata: # A property named `canonical_name` exposes the value. _canonical_name: utils.NormalizedName version: packaging_version.Version - platforms: List[str] + platforms: list[str] summary: str description: str - keywords: List[str] + keywords: list[str] home_page: str author: str - author_emails: List[_NameAndEmail] + author_emails: list[_NameAndEmail] license: str - supported_platforms: List[str] + supported_platforms: list[str] download_url: str - classifiers: List[str] + classifiers: list[str] maintainer: str - maintainer_emails: List[_NameAndEmail] - requires_dists: List[requirements.Requirement] + maintainer_emails: list[_NameAndEmail] + requires_dists: list[requirements.Requirement] requires_python: specifiers.SpecifierSet - requires_externals: List[str] - project_urls: List[_LabelAndURL] - provides_dists: List[str] - obsoletes_dists: List[str] + requires_externals: list[str] + project_urls: list[_LabelAndURL] + provides_dists: list[str] + obsoletes_dists: list[str] description_content_type: str - provides_extras: List[utils.NormalizedName] - dynamic_fields: List[DynamicField] + provides_extras: list[utils.NormalizedName] + dynamic_fields: list[DynamicField] def __init__( self, @@ -130,32 +99,32 @@ def __init__( version: packaging_version.Version, *, # 1.0 - platforms: Optional[Iterable[str]] = None, - summary: Optional[str] = None, - description: Optional[str] = None, - keywords: Optional[Iterable[str]] = None, - home_page: Optional[str] = None, - author: Optional[str] = None, - author_emails: Optional[Iterable[_NameAndEmail]] = None, - license: Optional[str] = None, + platforms: Iterable[str] | None = None, + summary: str | None = None, + description: str | None = None, + keywords: Iterable[str] | None = None, + home_page: str | None = None, + author: str | None = None, + author_emails: Iterable[_NameAndEmail] | None = None, + license: str | None = None, # 1.1 - supported_platforms: Optional[Iterable[str]] = None, - download_url: Optional[str] = None, - classifiers: Optional[Iterable[str]] = None, # TODO: OK? + supported_platforms: Iterable[str] | None = None, + download_url: str | None = None, + classifiers: Iterable[str] | None = None, # 1.2 - maintainer: Optional[str] = None, - maintainer_emails: Optional[Iterable[_NameAndEmail]] = None, - requires_dists: Optional[Iterable[requirements.Requirement]] = None, - requires_python: Optional[specifiers.SpecifierSet] = None, - requires_externals: Optional[Iterable[str]] = None, # TODO: OK? - project_urls: Optional[Iterable[_LabelAndURL]] = None, - provides_dists: Optional[Iterable[str]] = None, - obsoletes_dists: Optional[Iterable[str]] = None, + maintainer: str | None = None, + maintainer_emails: Iterable[_NameAndEmail] | None = None, + requires_dists: Iterable[requirements.Requirement] | None = None, + requires_python: specifiers.SpecifierSet | None = None, + requires_externals: Iterable[str] | None = None, + project_urls: Iterable[_LabelAndURL] | None = None, + provides_dists: Iterable[str] | None = None, + obsoletes_dists: Iterable[str] | None = None, # 2.1 - description_content_type: Optional[str] = None, # TODO: OK? - provides_extras: Optional[Iterable[utils.NormalizedName]] = None, + description_content_type: str | None = None, + provides_extras: Iterable[utils.NormalizedName] | None = None, # 2.2 - dynamic_fields: Optional[Iterable[DynamicField]] = None, + dynamic_fields: Iterable[DynamicField] | None = None, ) -> None: """ Set all attributes on the instance. @@ -193,8 +162,10 @@ def display_name(self) -> str: return self._display_name @display_name.setter - def display_name(self, value, /) -> None: - """Set the value for self.display_name and self.canonical_name.""" + def display_name(self, value: str) -> None: + """ + Set the value for self.display_name and self.canonical_name. + """ self._display_name = value self._canonical_name = utils.canonicalize_name(value) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 41be4695..fda418ce 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -3,6 +3,16 @@ from packaging import metadata, utils, version +class TestInit: + def test_defaults(self): + specified_attributes = {"display_name", "canonical_name", "version"} + metadata_ = metadata.Metadata("packaging", version.Version("2023.0.0")) + for attr in dir(metadata_): + if attr in specified_attributes or attr.startswith("_"): + continue + assert not getattr(metadata_, attr) + + class TestNameNormalization: version = version.Version("1.0.0") From e50086aec48918c625f89a0da8772a47be0aaf91 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 15 Jun 2022 19:42:03 -0700 Subject: [PATCH 7/9] Drop `InvalidMetadata` as it isn't used anywhere --- packaging/metadata.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packaging/metadata.py b/packaging/metadata.py index 9b0bfb0d..81405feb 100644 --- a/packaging/metadata.py +++ b/packaging/metadata.py @@ -16,12 +16,6 @@ _LabelAndURL = Tuple[str, str] -class InvalidMetadata(ValueError): - """ - Invalid metadata found. - """ - - @enum.unique class DynamicField(enum.Enum): From 6932abeb2b7e5b1b68364e706de701404c2648de Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 15 Jun 2022 19:42:17 -0700 Subject: [PATCH 8/9] Document `packaging.metadata.Metadata1 --- docs/index.rst | 1 + docs/metadata.rst | 86 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 docs/metadata.rst diff --git a/docs/index.rst b/docs/index.rst index aafdae83..8d72cbf0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,6 +26,7 @@ You can install packaging with ``pip``: markers requirements tags + metadata utils .. toctree:: diff --git a/docs/metadata.rst b/docs/metadata.rst new file mode 100644 index 00000000..890750e6 --- /dev/null +++ b/docs/metadata.rst @@ -0,0 +1,86 @@ +Metadata +========== + +.. currentmodule:: packaging.metadata + +A data representation for `core metadata`_. + + +Reference +--------- + +.. class:: DynamicField + + An :class:`enum.Enum` representing fields which can be listed in + the ``Dynamic`` field of `core metadata`_. Every valid field is + a name on this enum, upper-cased with any ``-`` replaced with ``_``. + Each value is the field name lower-cased (``-`` are kept). For + example, the ``Home-page`` field has a name of ``HOME_PAGE`` and a + value of ``home-page``. + + +.. class:: Metadata(name, version, *, platforms=None, summary=None, description=None, keywords=None, home_page=None, author=None, author_emails=None, license=None, supported_platforms=None, download_url=None, classifiers=None, maintainer=None, maintainer_emails=None, requires_dists=None, requires_python=None, requires_externals=None, project_urls=None, provides_dists= None, obsoletes_dists= None, description_content_type=None, provides_extras=None, dynamic_fields=None) + + A class representing the `core metadata`_ for a project. + + Every potential metadata field except for ``Metadata-Version`` is + represented by a parameter to the class' constructor. The required + metadata can be passed in positionally or via keyword, while all + optional metadata can only be passed in via keyword. + + Every parameter has a matching attribute on instances, + except for *name* (see :attr:`display_name` and + :attr:`canonical_name`). Any parameter that accepts an + :class:`~collections.abc.Iterable` is represented as a + :class:`list` on the corresponding attribute. + + :param str name: ``Name``. + :param packaging.version.Version version: ``Version`` (note + that this is different than ``Metadata-Version``). + :param Iterable[str] platforms: ``Platform``. + :param str summary: ``Summary``. + :param str description: ``Description``. + :param Iterable[str] keywords: ``Keywords``. + :param str home_page: ``Home-Page``. + :param str author: ``Author``. + :param Iterable[tuple[str | None, str]] author_emails: ``Author-Email`` + where the two-item tuple represents the name and email of the author, + respectively. + :param str license: ``License``. + :param Iterable[str] supported_platforms: ``Supported-Platform``. + :param str download_url: ``Download-URL``. + :param Iterable[str] classifiers: ``Classifier``. + :param str maintainer: ``Maintainer``. + :param Iterable[tuple[str | None, str]] maintainer_emails: ``Maintainer-Email``, + where the two-item tuple represents the name and email of the maintainer, + respectively. + :param Iterable[packaging.requirements.Requirement] requires_dists: ``Requires-Dist``. + :param packaging.specifiers.SpecifierSet requires_python: ``Requires-Python``. + :param Iterable[str] requires_externals: ``Requires-External``. + :param tuple[str, str] project_urls: ``Project-URL``. + :param Iterable[str] provides_dists: ``Provides-Dist``. + :param Iterable[str] obsoletes_dists: ``Obsoletes-Dist``. + :param str description_content_type: ``Description-Content-Type``. + :param Iterable[packaging.utils.NormalizedName] provides_extras: ``Provides-Extra``. + :param Iterable[DynamicField] dynamic_fields: ``Dynamic``. + + Attributes not directly corresponding to a parameter are: + + .. attribute:: display_name + + The project name to be displayed to users (i.e. not normalized). + Initially set based on the *name* parameter. + Setting this attribute will also update :attr:`canonical_name`. + + .. attribute:: canonical_name + + The normalized project name as per + :func:`packaging.utils.canonicalize_name`. The attribute is + read-only and automatically calculated based on the value of + :attr:`display_name`. + + +.. _`core metadata`: https://packaging.python.org/en/latest/specifications/core-metadata/ +.. _`project metadata`: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ +.. _`source distribution`: https://packaging.python.org/en/latest/specifications/source-distribution-format/ +.. _`binary distrubtion`: https://packaging.python.org/en/latest/specifications/binary-distribution-format/ From 64a5cef9ac46d005b343f71e77794b4d2c73b55f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 15 Jun 2022 19:46:14 -0700 Subject: [PATCH 9/9] Update pre-commit for flake8 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce2e4a43..f0b033f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: hooks: - id: isort - - repo: https://gitlab.com/PyCQA/flake8 + - repo: https://github.com/PyCQA/flake8 rev: "3.9.2" hooks: - id: flake8