From ed369c8dda9afb0832f965128680bb332bc7bdb4 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 27 Sep 2024 01:19:18 -0400 Subject: [PATCH] chore: reformat to match packaging Signed-off-by: Henry Schreiner --- docs/conf.py | 29 +- noxfile.py | 29 +- pyproject.toml | 4 - pyproject_metadata/__init__.py | 200 +++++---- pyproject_metadata/constants.py | 151 ++++--- pyproject_metadata/errors.py | 11 +- pyproject_metadata/project_table.py | 102 +++-- pyproject_metadata/pyproject.py | 111 +++-- tests/test_internals.py | 12 +- tests/test_rfc822.py | 142 +++--- tests/test_standard_metadata.py | 662 ++++++++++++++-------------- 11 files changed, 719 insertions(+), 734 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 49e4991..710f747 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,12 +12,11 @@ import pyproject_metadata - # -- Project information ----------------------------------------------------- -project = 'pyproject-metadata' -copyright = '2021, Filipe Laíns' -author = 'Filipe Laíns' +project = "pyproject-metadata" +copyright = "2021, Filipe Laíns" +author = "Filipe Laíns" # The short X.Y version version = pyproject_metadata.__version__ @@ -31,38 +30,38 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.viewcode', - 'sphinx_autodoc_typehints', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.viewcode", + "sphinx_autodoc_typehints", ] intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'packaging': ('https://packaging.pypa.io/en/latest/', None), + "python": ("https://docs.python.org/3/", None), + "packaging": ("https://packaging.pypa.io/en/latest/", None), } # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] -default_role = 'any' +default_role = "any" # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'furo' -html_title = f'pyproject-metadata {version}' +html_theme = "furo" +html_title = f"pyproject-metadata {version}" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named 'default.css' will overwrite the builtin 'default.css'. # html_static_path = ['_static'] -autoclass_content = 'both' +autoclass_content = "both" diff --git a/noxfile.py b/noxfile.py index 9660927..cbeb65a 100644 --- a/noxfile.py +++ b/noxfile.py @@ -5,33 +5,32 @@ import nox - -nox.options.sessions = ['mypy', 'test'] +nox.options.sessions = ["mypy", "test"] nox.options.reuse_existing_virtualenvs = True -@nox.session(python='3.7') +@nox.session(python="3.7") def mypy(session: nox.Session) -> None: - session.install('.', 'mypy', 'nox', 'pytest') + session.install(".", "mypy", "nox", "pytest") - session.run('mypy', 'pyproject_metadata', 'tests', 'noxfile.py') + session.run("mypy", "pyproject_metadata", "tests", "noxfile.py") -@nox.session(python=['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]) def test(session: nox.Session) -> None: - htmlcov_output = os.path.join(session.virtualenv.location, 'htmlcov') + htmlcov_output = os.path.join(session.virtualenv.location, "htmlcov") xmlcov_output = os.path.join( - session.virtualenv.location, f'coverage-{session.python}.xml' + session.virtualenv.location, f"coverage-{session.python}.xml" ) - session.install('.[test]') + session.install(".[test]") session.run( - 'pytest', - '--cov', - f'--cov-report=html:{htmlcov_output}', - f'--cov-report=xml:{xmlcov_output}', - '--cov-report=term-missing', - 'tests/', + "pytest", + "--cov", + f"--cov-report=html:{htmlcov_output}", + f"--cov-report=xml:{xmlcov_output}", + "--cov-report=term-missing", + "tests/", *session.posargs, ) diff --git a/pyproject.toml b/pyproject.toml index ac01cdc..f31c40e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,11 +91,7 @@ ignore = [ "ISC001", # conflicts with formatter "PLR09", # Design related (too many X) ] -isort.lines-after-imports = 2 -isort.lines-between-types = 1 -[tool.ruff.format] -quote-style = "single" [tool.coverage] diff --git a/pyproject_metadata/__init__.py b/pyproject_metadata/__init__.py index ea7d543..e6267a4 100644 --- a/pyproject_metadata/__init__.py +++ b/pyproject_metadata/__init__.py @@ -19,7 +19,6 @@ from .errors import ConfigurationError, ConfigurationWarning, ErrorCollector from .pyproject import License, PyProjectReader, Readme - if typing.TYPE_CHECKING: from collections.abc import Mapping from typing import Any @@ -38,21 +37,20 @@ import packaging.utils import packaging.version - -__version__ = '0.9.0b5' +__version__ = "0.9.0b5" __all__ = [ - 'ConfigurationError', - 'ConfigurationWarning', - 'License', - 'RFC822Message', - 'RFC822Policy', - 'Readme', - 'StandardMetadata', - 'field_to_metadata', - 'extras_build_system', - 'extras_project', - 'extras_top_level', + "ConfigurationError", + "ConfigurationWarning", + "License", + "RFC822Message", + "RFC822Policy", + "Readme", + "StandardMetadata", + "field_to_metadata", + "extras_build_system", + "extras_project", + "extras_top_level", ] @@ -73,13 +71,13 @@ def extras_top_level(pyproject_table: Mapping[str, Any]) -> set[str]: def extras_build_system(pyproject_table: Mapping[str, Any]) -> set[str]: return ( - set(pyproject_table.get('build-system', [])) + set(pyproject_table.get("build-system", [])) - constants.KNOWN_BUILD_SYSTEM_FIELDS ) def extras_project(pyproject_table: Mapping[str, Any]) -> set[str]: - return set(pyproject_table.get('project', [])) - constants.KNOWN_PROJECT_FIELDS + return set(pyproject_table.get("project", [])) - constants.KNOWN_PROJECT_FIELDS @dataclasses.dataclass @@ -114,13 +112,13 @@ class _JSonMessageSetter: def __setitem__(self, name: str, value: str | None) -> None: name = name.lower() - key = name.replace('-', '_') + key = name.replace("-", "_") if value is None: return - if name == 'keywords': - values = (x.strip() for x in value.split(',')) + if name == "keywords": + values = (x.strip() for x in value.split(",")) self.data[key] = [x for x in values if x] elif name in constants.KNOWN_MULTIUSE: entry = self.data.setdefault(key, []) @@ -130,7 +128,7 @@ def __setitem__(self, name: str, value: str | None) -> None: self.data[key] = value def set_payload(self, payload: str) -> None: - self['description'] = payload + self["description"] = payload class RFC822Policy(email.policy.EmailPolicy): @@ -148,7 +146,7 @@ def header_store_parse(self, name: str, value: str) -> tuple[str, str]: msg = f'Unknown field "{name}"' raise ConfigurationError(msg, key=name) size = len(name) + 2 - value = value.replace('\n', '\n' + ' ' * size) + value = value.replace("\n", "\n" + " " * size) return (name, value) @@ -165,7 +163,7 @@ def __init__(self) -> None: def as_bytes( self, unixfrom: bool = False, policy: email.policy.Policy | None = None ) -> bytes: - return self.as_string(unixfrom, policy=policy).encode('utf-8') + return self.as_string(unixfrom, policy=policy).encode("utf-8") @dataclasses.dataclass @@ -206,9 +204,9 @@ def __post_init__(self) -> None: self.validate() def __setattr__(self, name: str, value: Any) -> None: - if self._locked_metadata and name.replace('_', '-') not in set(self.dynamic) | { - 'metadata-version', - 'dynamic-metadata', + if self._locked_metadata and name.replace("_", "-") not in set(self.dynamic) | { + "metadata-version", + "dynamic-metadata", }: msg = f'Field "{name}" is not dynamic' raise AttributeError(msg) @@ -218,32 +216,32 @@ def validate(self, *, warn: bool = True) -> None: # noqa: C901 errors = ErrorCollector(collect_errors=self.all_errors) if self.auto_metadata_version not in constants.KNOWN_METADATA_VERSIONS: - msg = f'The metadata_version must be one of {constants.KNOWN_METADATA_VERSIONS} or None (default)' + msg = f"The metadata_version must be one of {constants.KNOWN_METADATA_VERSIONS} or None (default)" errors.config_error(msg) # See https://packaging.python.org/en/latest/specifications/core-metadata/#name and # https://packaging.python.org/en/latest/specifications/name-normalization/#name-format if not re.match( - r'^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$', self.name, re.IGNORECASE + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", self.name, re.IGNORECASE ): msg = ( f'Invalid project name "{self.name}". A valid name consists only of ASCII letters and ' - 'numbers, period, underscore and hyphen. It must start and end with a letter or number' + "numbers, period, underscore and hyphen. It must start and end with a letter or number" ) - errors.config_error(msg, key='project.name') + errors.config_error(msg, key="project.name") if self.license_files is not None and isinstance(self.license, License): msg = '"project.license-files" must not be used when "project.license" is not a SPDX license expression' - errors.config_error(msg, key='project.license-files') + errors.config_error(msg, key="project.license-files") if isinstance(self.license, str) and any( - c.startswith('License ::') for c in self.classifiers + c.startswith("License ::") for c in self.classifiers ): msg = 'Setting "project.license" to an SPDX license expression is not compatible with "License ::" classifiers' - errors.config_error(msg, key='project.license') + errors.config_error(msg, key="project.license") if warn: - if self.description and '\n' in self.description: + if self.description and "\n" in self.description: warnings.warn( 'The one-line summary "project.description" should not contain more than one line. Readers might merge or truncate newlines.', ConfigurationWarning, @@ -256,7 +254,7 @@ def validate(self, *, warn: bool = True) -> None: # noqa: C901 ConfigurationWarning, stacklevel=2, ) - elif any(c.startswith('License ::') for c in self.classifiers): + elif any(c.startswith("License ::") for c in self.classifiers): warnings.warn( '"License ::" classifiers are deprecated for metadata >= 2.4, use a SPDX license expression for "project.license" instead', ConfigurationWarning, @@ -268,16 +266,16 @@ def validate(self, *, warn: bool = True) -> None: # noqa: C901 and self.auto_metadata_version in constants.PRE_SPDX_METADATA_VERSIONS ): msg = 'Setting "project.license" to an SPDX license expression is supported only when emitting metadata version >= 2.4' - errors.config_error(msg, key='project.license') + errors.config_error(msg, key="project.license") if ( self.license_files is not None and self.auto_metadata_version in constants.PRE_SPDX_METADATA_VERSIONS ): msg = '"project.license-files" is supported only when emitting metadata version >= 2.4' - errors.config_error(msg, key='project.license-files') + errors.config_error(msg, key="project.license-files") - errors.finalize('Metadata validation failed') + errors.finalize("Metadata validation failed") @property def auto_metadata_version(self) -> str: @@ -285,10 +283,10 @@ def auto_metadata_version(self) -> str: return self.metadata_version if isinstance(self.license, str) or self.license_files is not None: - return '2.4' + return "2.4" if self.dynamic_metadata: - return '2.2' - return '2.1' + return "2.2" + return "2.1" @property def canonical_name(self) -> str: @@ -308,20 +306,20 @@ def from_pyproject( # noqa: C901 pyproject = PyProjectReader(collect_errors=all_errors) pyproject_table: PyProjectTable = data # type: ignore[assignment] - if 'project' not in pyproject_table: + if "project" not in pyproject_table: msg = 'Section "project" missing in pyproject.toml' - pyproject.config_error(msg, key='project') - pyproject.finalize('Failed to parse pyproject.toml') - msg = 'Unreachable code' # pragma: no cover + pyproject.config_error(msg, key="project") + pyproject.finalize("Failed to parse pyproject.toml") + msg = "Unreachable code" # pragma: no cover raise AssertionError(msg) # pragma: no cover - project = pyproject_table['project'] + project = pyproject_table["project"] project_dir = pathlib.Path(project_dir) if not allow_extra_keys: extra_keys = extras_project(data) if extra_keys: - extra_keys_str = ', '.join(sorted(f'"{k}"' for k in extra_keys)) + extra_keys_str = ", ".join(sorted(f'"{k}"' for k in extra_keys)) msg = f'Extra keys present in "project": {extra_keys_str}' if allow_extra_keys is None: warnings.warn(msg, ConfigurationWarning, stacklevel=2) @@ -331,24 +329,24 @@ def from_pyproject( # noqa: C901 dynamic = pyproject.get_dynamic(project) for field in dynamic: - if field in data['project']: + if field in data["project"]: msg = f'Field "project.{field}" declared as dynamic in "project.dynamic" but is defined' pyproject.config_error(msg, key=field) - raw_name = project.get('name') - name = 'UNKNOWN' + raw_name = project.get("name") + name = "UNKNOWN" if raw_name is None: msg = 'Field "project.name" missing' - pyproject.config_error(msg, key='name') + pyproject.config_error(msg, key="name") else: - tmp_name = pyproject.ensure_str(raw_name, 'project.name') + tmp_name = pyproject.ensure_str(raw_name, "project.name") if tmp_name is not None: name = tmp_name - version: packaging.version.Version | None = packaging.version.Version('0.0.0') - raw_version = project.get('version') + version: packaging.version.Version | None = packaging.version.Version("0.0.0") + raw_version = project.get("version") if raw_version is not None: - version_string = pyproject.ensure_str(raw_version, 'project.version') + version_string = pyproject.ensure_str(raw_version, "project.version") if version_string is not None: try: version = ( @@ -358,26 +356,26 @@ def from_pyproject( # noqa: C901 ) except packaging.version.InvalidVersion: msg = f'Invalid "project.version" value, expecting a valid PEP 440 version (got "{version_string}")' - pyproject.config_error(msg, key='project.version') - elif 'version' not in dynamic: + pyproject.config_error(msg, key="project.version") + elif "version" not in dynamic: msg = 'Field "project.version" missing and "version" not specified in "project.dynamic"' - pyproject.config_error(msg, key='version') + pyproject.config_error(msg, key="version") # Description fills Summary, which cannot be multiline # However, throwing an error isn't backward compatible, # so leave it up to the users for now. - project_description_raw = project.get('description') + project_description_raw = project.get("description") description = ( - pyproject.ensure_str(project_description_raw, 'project.description') + pyproject.ensure_str(project_description_raw, "project.description") if project_description_raw is not None else None ) - requires_python_raw = project.get('requires-python') + requires_python_raw = project.get("requires-python") requires_python = None if requires_python_raw is not None: requires_python_string = pyproject.ensure_str( - requires_python_raw, 'project.requires-python' + requires_python_raw, "project.requires-python" ) if requires_python_string is not None: try: @@ -386,7 +384,7 @@ def from_pyproject( # noqa: C901 ) except packaging.specifiers.InvalidSpecifier: msg = f'Invalid "project.requires-python" value, expecting a valid specifier set (got "{requires_python_string}")' - pyproject.config_error(msg, key='project.requires-python') + pyproject.config_error(msg, key="project.requires-python") self = None with pyproject.collect(): @@ -402,27 +400,27 @@ def from_pyproject( # noqa: C901 optional_dependencies=pyproject.get_optional_dependencies(project), entrypoints=pyproject.get_entrypoints(project), authors=pyproject.ensure_people( - project.get('authors', []), 'project.authors' + project.get("authors", []), "project.authors" ), maintainers=pyproject.ensure_people( - project.get('maintainers', []), 'project.maintainers' + project.get("maintainers", []), "project.maintainers" ), - urls=pyproject.ensure_dict(project.get('urls', {}), 'project.urls') + urls=pyproject.ensure_dict(project.get("urls", {}), "project.urls") or {}, classifiers=pyproject.ensure_list( - project.get('classifiers', []), 'project.classifiers' + project.get("classifiers", []), "project.classifiers" ) or [], keywords=pyproject.ensure_list( - project.get('keywords', []), 'project.keywords' + project.get("keywords", []), "project.keywords" ) or [], scripts=pyproject.ensure_dict( - project.get('scripts', {}), 'project.scripts' + project.get("scripts", {}), "project.scripts" ) or {}, gui_scripts=pyproject.ensure_dict( - project.get('gui-scripts', {}), 'project.gui-scripts' + project.get("gui-scripts", {}), "project.gui-scripts" ) or {}, dynamic=dynamic, @@ -432,7 +430,7 @@ def from_pyproject( # noqa: C901 ) self._locked_metadata = True - pyproject.finalize('Failed to parse pyproject.toml') + pyproject.finalize("Failed to parse pyproject.toml") assert self is not None return self @@ -453,73 +451,73 @@ def _write_metadata( # noqa: C901 ) -> None: self.validate(warn=False) - smart_message['Metadata-Version'] = self.auto_metadata_version - smart_message['Name'] = self.name + smart_message["Metadata-Version"] = self.auto_metadata_version + smart_message["Name"] = self.name if not self.version: - msg = 'Missing version field' + msg = "Missing version field" raise ConfigurationError(msg) - smart_message['Version'] = str(self.version) + smart_message["Version"] = str(self.version) # skip 'Platform' # skip 'Supported-Platform' if self.description: - smart_message['Summary'] = self.description - smart_message['Keywords'] = ','.join(self.keywords) or None - if 'homepage' in self.urls: - smart_message['Home-page'] = self.urls['homepage'] + smart_message["Summary"] = self.description + smart_message["Keywords"] = ",".join(self.keywords) or None + if "homepage" in self.urls: + smart_message["Home-page"] = self.urls["homepage"] # skip 'Download-URL' - smart_message['Author'] = self._name_list(self.authors) - smart_message['Author-Email'] = self._email_list(self.authors) - smart_message['Maintainer'] = self._name_list(self.maintainers) - smart_message['Maintainer-Email'] = self._email_list(self.maintainers) + smart_message["Author"] = self._name_list(self.authors) + smart_message["Author-Email"] = self._email_list(self.authors) + smart_message["Maintainer"] = self._name_list(self.maintainers) + smart_message["Maintainer-Email"] = self._email_list(self.maintainers) if isinstance(self.license, License): - smart_message['License'] = self.license.text + smart_message["License"] = self.license.text elif isinstance(self.license, str): - smart_message['License-Expression'] = self.license + smart_message["License-Expression"] = self.license if self.license_files is not None: for license_file in sorted(set(self.license_files)): - smart_message['License-File'] = os.fspath(license_file.as_posix()) + smart_message["License-File"] = os.fspath(license_file.as_posix()) for classifier in self.classifiers: - smart_message['Classifier'] = classifier + smart_message["Classifier"] = classifier # skip 'Provides-Dist' # skip 'Obsoletes-Dist' # skip 'Requires-External' for name, url in self.urls.items(): - smart_message['Project-URL'] = f'{name.capitalize()}, {url}' + smart_message["Project-URL"] = f"{name.capitalize()}, {url}" if self.requires_python: - smart_message['Requires-Python'] = str(self.requires_python) + smart_message["Requires-Python"] = str(self.requires_python) for dep in self.dependencies: - smart_message['Requires-Dist'] = str(dep) + smart_message["Requires-Dist"] = str(dep) for extra, requirements in self.optional_dependencies.items(): - norm_extra = extra.replace('.', '-').replace('_', '-').lower() - smart_message['Provides-Extra'] = norm_extra + norm_extra = extra.replace(".", "-").replace("_", "-").lower() + smart_message["Provides-Extra"] = norm_extra for requirement in requirements: - smart_message['Requires-Dist'] = str( + smart_message["Requires-Dist"] = str( self._build_extra_req(norm_extra, requirement) ) if self.readme: if self.readme.content_type: - smart_message['Description-Content-Type'] = self.readme.content_type + smart_message["Description-Content-Type"] = self.readme.content_type smart_message.set_payload(self.readme.text) # Core Metadata 2.2 - if self.auto_metadata_version != '2.1': + if self.auto_metadata_version != "2.1": for field in self.dynamic_metadata: - if field.lower() in {'name', 'version', 'dynamic'}: - msg = f'Field cannot be set as dynamic metadata: {field}' + if field.lower() in {"name", "version", "dynamic"}: + msg = f"Field cannot be set as dynamic metadata: {field}" raise ConfigurationError(msg) if field.lower() not in constants.KNOWN_METADATA_FIELDS: - msg = f'Field is not known: {field}' + msg = f"Field is not known: {field}" raise ConfigurationError(msg) - smart_message['Dynamic'] = field + smart_message["Dynamic"] = field def _name_list(self, people: list[tuple[str, str | None]]) -> str | None: - return ', '.join(name for name, email_ in people if not email_) or None + return ", ".join(name for name, email_ in people if not email_) or None def _email_list(self, people: list[tuple[str, str | None]]) -> str | None: return ( - ', '.join( + ", ".join( email.utils.formataddr((name, _email)) for name, _email in people if _email @@ -535,7 +533,7 @@ def _build_extra_req( # append or add our extra marker requirement = copy.copy(requirement) if requirement.marker: - if 'or' in requirement.marker._markers: + if "or" in requirement.marker._markers: requirement.marker = packaging.markers.Marker( f'({requirement.marker}) and extra == "{extra}"' ) diff --git a/pyproject_metadata/constants.py b/pyproject_metadata/constants.py index 4d636ec..a33145d 100644 --- a/pyproject_metadata/constants.py +++ b/pyproject_metadata/constants.py @@ -1,16 +1,15 @@ from __future__ import annotations - __all__ = [ - 'KNOWN_BUILD_SYSTEM_FIELDS', - 'KNOWN_METADATA_FIELDS', - 'KNOWN_METADATA_VERSIONS', - 'KNOWN_METADATA_VERSIONS', - 'KNOWN_MULTIUSE', - 'KNOWN_PROJECT_FIELDS', - 'KNOWN_TOPLEVEL_FIELDS', - 'PRE_SPDX_METADATA_VERSIONS', - 'PROJECT_TO_METADATA', + "KNOWN_BUILD_SYSTEM_FIELDS", + "KNOWN_METADATA_FIELDS", + "KNOWN_METADATA_VERSIONS", + "KNOWN_METADATA_VERSIONS", + "KNOWN_MULTIUSE", + "KNOWN_PROJECT_FIELDS", + "KNOWN_TOPLEVEL_FIELDS", + "PRE_SPDX_METADATA_VERSIONS", + "PROJECT_TO_METADATA", ] @@ -18,80 +17,80 @@ def __dir__() -> list[str]: return __all__ -KNOWN_METADATA_VERSIONS = {'2.1', '2.2', '2.3', '2.4'} -PRE_SPDX_METADATA_VERSIONS = {'2.1', '2.2', '2.3'} +KNOWN_METADATA_VERSIONS = {"2.1", "2.2", "2.3", "2.4"} +PRE_SPDX_METADATA_VERSIONS = {"2.1", "2.2", "2.3"} PROJECT_TO_METADATA = { - 'authors': frozenset(['Author', 'Author-Email']), - 'classifiers': frozenset(['Classifier']), - 'dependencies': frozenset(['Requires-Dist']), - 'description': frozenset(['Summary']), - 'dynamic': frozenset(), - 'entry-points': frozenset(), - 'gui-scripts': frozenset(), - 'keywords': frozenset(['Keywords']), - 'license': frozenset(['License', 'License-Expression']), - 'license-files': frozenset(['License-File']), - 'maintainers': frozenset(['Maintainer', 'Maintainer-Email']), - 'name': frozenset(['Name']), - 'optional-dependencies': frozenset(['Provides-Extra', 'Requires-Dist']), - 'readme': frozenset(['Description', 'Description-Content-Type']), - 'requires-python': frozenset(['Requires-Python']), - 'scripts': frozenset(), - 'urls': frozenset(['Project-URL']), - 'version': frozenset(['Version']), + "authors": frozenset(["Author", "Author-Email"]), + "classifiers": frozenset(["Classifier"]), + "dependencies": frozenset(["Requires-Dist"]), + "description": frozenset(["Summary"]), + "dynamic": frozenset(), + "entry-points": frozenset(), + "gui-scripts": frozenset(), + "keywords": frozenset(["Keywords"]), + "license": frozenset(["License", "License-Expression"]), + "license-files": frozenset(["License-File"]), + "maintainers": frozenset(["Maintainer", "Maintainer-Email"]), + "name": frozenset(["Name"]), + "optional-dependencies": frozenset(["Provides-Extra", "Requires-Dist"]), + "readme": frozenset(["Description", "Description-Content-Type"]), + "requires-python": frozenset(["Requires-Python"]), + "scripts": frozenset(), + "urls": frozenset(["Project-URL"]), + "version": frozenset(["Version"]), } -KNOWN_TOPLEVEL_FIELDS = {'build-system', 'project', 'tool'} -KNOWN_BUILD_SYSTEM_FIELDS = {'backend-path', 'build-backend', 'requires'} +KNOWN_TOPLEVEL_FIELDS = {"build-system", "project", "tool"} +KNOWN_BUILD_SYSTEM_FIELDS = {"backend-path", "build-backend", "requires"} KNOWN_PROJECT_FIELDS = set(PROJECT_TO_METADATA) KNOWN_METADATA_FIELDS = { - 'author', - 'author-email', - 'classifier', - 'description', - 'description-content-type', - 'download-url', # Not specified via pyproject standards - 'dynamic', # Can't be in dynamic - 'home-page', # Not specified via pyproject standards - 'keywords', - 'license', - 'license-expression', - 'license-file', - 'maintainer', - 'maintainer-email', - 'metadata-version', - 'name', # Can't be in dynamic - 'obsoletes', # Deprecated - 'obsoletes-dist', # Rarely used - 'platform', # Not specified via pyproject standards - 'project-url', - 'provides', # Deprecated - 'provides-dist', # Rarely used - 'provides-extra', - 'requires', # Deprecated - 'requires-dist', - 'requires-external', # Not specified via pyproject standards - 'requires-python', - 'summary', - 'supported-platform', # Not specified via pyproject standards - 'version', # Can't be in dynamic + "author", + "author-email", + "classifier", + "description", + "description-content-type", + "download-url", # Not specified via pyproject standards + "dynamic", # Can't be in dynamic + "home-page", # Not specified via pyproject standards + "keywords", + "license", + "license-expression", + "license-file", + "maintainer", + "maintainer-email", + "metadata-version", + "name", # Can't be in dynamic + "obsoletes", # Deprecated + "obsoletes-dist", # Rarely used + "platform", # Not specified via pyproject standards + "project-url", + "provides", # Deprecated + "provides-dist", # Rarely used + "provides-extra", + "requires", # Deprecated + "requires-dist", + "requires-external", # Not specified via pyproject standards + "requires-python", + "summary", + "supported-platform", # Not specified via pyproject standards + "version", # Can't be in dynamic } KNOWN_MULTIUSE = { - 'dynamic', - 'platform', - 'provides-extra', - 'supported-platform', - 'license-file', - 'classifier', - 'requires-dist', - 'requires-external', - 'project-url', - 'provides-dist', - 'obsoletes-dist', - 'requires', # Deprecated - 'obsoletes', # Deprecated - 'provides', # Deprecated + "dynamic", + "platform", + "provides-extra", + "supported-platform", + "license-file", + "classifier", + "requires-dist", + "requires-external", + "project-url", + "provides-dist", + "obsoletes-dist", + "requires", # Deprecated + "obsoletes", # Deprecated + "provides", # Deprecated } diff --git a/pyproject_metadata/errors.py b/pyproject_metadata/errors.py index b993051..abfaa34 100644 --- a/pyproject_metadata/errors.py +++ b/pyproject_metadata/errors.py @@ -6,12 +6,11 @@ import sys import typing - __all__ = [ - 'ConfigurationError', - 'ConfigurationWarning', - 'ExceptionGroup', - 'ErrorCollector', + "ConfigurationError", + "ConfigurationWarning", + "ExceptionGroup", + "ErrorCollector", ] @@ -50,7 +49,7 @@ def __init__(self, message: str, exceptions: list[Exception]) -> None: self.exceptions = exceptions def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.message!r}, {self.exceptions!r})' + return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})" @dataclasses.dataclass diff --git a/pyproject_metadata/project_table.py b/pyproject_metadata/project_table.py index d0e1217..8575af3 100644 --- a/pyproject_metadata/project_table.py +++ b/pyproject_metadata/project_table.py @@ -3,10 +3,8 @@ from __future__ import annotations import sys - from typing import Any, Dict, List, Union - if sys.version_info < (3, 11): from typing_extensions import Required else: @@ -19,12 +17,12 @@ __all__ = [ - 'ContactTable', - 'LicenseTable', - 'ReadmeTable', - 'ProjectTable', - 'BuildSystemTable', - 'PyProjectTable', + "ContactTable", + "LicenseTable", + "ReadmeTable", + "ProjectTable", + "BuildSystemTable", + "PyProjectTable", ] @@ -43,47 +41,47 @@ class LicenseTable(TypedDict, total=False): ReadmeTable = TypedDict( - 'ReadmeTable', {'file': str, 'text': str, 'content-type': str}, total=False + "ReadmeTable", {"file": str, "text": str, "content-type": str}, total=False ) ProjectTable = TypedDict( - 'ProjectTable', + "ProjectTable", { - 'name': Required[str], - 'version': str, - 'description': str, - 'license': Union[LicenseTable, str], - 'license-files': List[str], - 'readme': Union[str, ReadmeTable], - 'requires-python': str, - 'dependencies': List[str], - 'optional-dependencies': Dict[str, List[str]], - 'entry-points': Dict[str, Dict[str, str]], - 'authors': List[ContactTable], - 'maintainers': List[ContactTable], - 'urls': Dict[str, str], - 'classifiers': List[str], - 'keywords': List[str], - 'scripts': Dict[str, str], - 'gui-scripts': Dict[str, str], - 'dynamic': List[ + "name": Required[str], + "version": str, + "description": str, + "license": Union[LicenseTable, str], + "license-files": List[str], + "readme": Union[str, ReadmeTable], + "requires-python": str, + "dependencies": List[str], + "optional-dependencies": Dict[str, List[str]], + "entry-points": Dict[str, Dict[str, str]], + "authors": List[ContactTable], + "maintainers": List[ContactTable], + "urls": Dict[str, str], + "classifiers": List[str], + "keywords": List[str], + "scripts": Dict[str, str], + "gui-scripts": Dict[str, str], + "dynamic": List[ Literal[ - 'authors', - 'classifiers', - 'dependencies', - 'description', - 'dynamic', - 'entry-points', - 'gui-scripts', - 'keywords', - 'license', - 'maintainers', - 'optional-dependencies', - 'readme', - 'requires-python', - 'scripts', - 'urls', - 'version', + "authors", + "classifiers", + "dependencies", + "description", + "dynamic", + "entry-points", + "gui-scripts", + "keywords", + "license", + "maintainers", + "optional-dependencies", + "readme", + "requires-python", + "scripts", + "urls", + "version", ] ], }, @@ -91,21 +89,21 @@ class LicenseTable(TypedDict, total=False): ) BuildSystemTable = TypedDict( - 'BuildSystemTable', + "BuildSystemTable", { - 'build-backend': str, - 'requires': List[str], - 'backend-path': List[str], + "build-backend": str, + "requires": List[str], + "backend-path": List[str], }, total=False, ) PyProjectTable = TypedDict( - 'PyProjectTable', + "PyProjectTable", { - 'build-system': BuildSystemTable, - 'project': ProjectTable, - 'tool': Dict[str, Any], + "build-system": BuildSystemTable, + "project": ProjectTable, + "tool": Dict[str, Any], }, total=False, ) diff --git a/pyproject_metadata/pyproject.py b/pyproject_metadata/pyproject.py index ef29afd..475d263 100644 --- a/pyproject_metadata/pyproject.py +++ b/pyproject_metadata/pyproject.py @@ -9,10 +9,9 @@ from .errors import ErrorCollector - __all__ = [ - 'License', - 'Readme', + "License", + "Readme", ] @@ -72,7 +71,7 @@ def ensure_dict(self, val: dict[str, str], key: str) -> dict[str, str] | None: for subkey, item in val.items(): if not isinstance(item, str): msg = f'Field "{key}.{subkey}" has an invalid type, expecting a string (got "{item}")' - self.config_error(msg, key=f'{key}.{subkey}') + self.config_error(msg, key=f"{key}.{subkey}") return None return val @@ -94,48 +93,48 @@ def ensure_people( ) self.config_error(msg, key=key) return [] - return [(entry.get('name', 'Unknown'), entry.get('email')) for entry in val] + return [(entry.get("name", "Unknown"), entry.get("email")) for entry in val] def get_license( self, project: ProjectTable, project_dir: pathlib.Path ) -> License | str | None: - val = project.get('license') + val = project.get("license") if val is None: return None if isinstance(val, str): return val if isinstance(val, dict): - _license = self.ensure_dict(val, 'project.license') # type: ignore[arg-type] + _license = self.ensure_dict(val, "project.license") # type: ignore[arg-type] if _license is None: return None else: msg = f'Field "project.license" has an invalid type, expecting a string or dictionary of strings (got "{val}")' - self.config_error(msg, key='project.license') + self.config_error(msg, key="project.license") return None for field in _license: - if field not in ('file', 'text'): + if field not in ("file", "text"): msg = f'Unexpected field "project.license.{field}"' - self.config_error(msg, key=f'project.license.{field}') + self.config_error(msg, key=f"project.license.{field}") return None file: pathlib.Path | None = None - filename = _license.get('file') - text = _license.get('text') + filename = _license.get("file") + text = _license.get("text") if (filename and text) or (not filename and not text): msg = f'Invalid "project.license" value, expecting either "file" or "text" (got "{_license}")' - self.config_error(msg, key='project.license') + self.config_error(msg, key="project.license") return None if filename: file = project_dir.joinpath(filename) if not file.is_file(): msg = f'License file not found ("{filename}")' - self.config_error(msg, key='project.license.file') + self.config_error(msg, key="project.license.file") return None - text = file.read_text(encoding='utf-8') + text = file.read_text(encoding="utf-8") assert text is not None return License(text, file) @@ -143,10 +142,10 @@ def get_license( def get_license_files( self, project: ProjectTable, project_dir: pathlib.Path ) -> list[pathlib.Path] | None: - license_files = project.get('license-files') + license_files = project.get("license-files") if license_files is None: return None - if self.ensure_list(license_files, 'project.license-files') is None: + if self.ensure_list(license_files, "project.license-files") is None: return None return list(self._get_files_from_globs(project_dir, license_files)) @@ -154,7 +153,7 @@ def get_license_files( def get_readme( # noqa: C901 self, project: ProjectTable, project_dir: pathlib.Path ) -> Readme | None: - if 'readme' not in project: + if "readme" not in project: return None filename: str | None = None @@ -162,79 +161,79 @@ def get_readme( # noqa: C901 text: str | None = None content_type: str | None = None - readme = project['readme'] + readme = project["readme"] if isinstance(readme, str): # readme is a file text = None filename = readme - if filename.endswith('.md'): - content_type = 'text/markdown' - elif filename.endswith('.rst'): - content_type = 'text/x-rst' + if filename.endswith(".md"): + content_type = "text/markdown" + elif filename.endswith(".rst"): + content_type = "text/x-rst" else: msg = f'Could not infer content type for readme file "{filename}"' - self.config_error(msg, key='project.readme') + self.config_error(msg, key="project.readme") return None elif isinstance(readme, dict): # readme is a dict containing either 'file' or 'text', and content-type for field in readme: - if field not in ('content-type', 'file', 'text'): + if field not in ("content-type", "file", "text"): msg = f'Unexpected field "project.readme.{field}"' - self.config_error(msg, key=f'project.readme.{field}') + self.config_error(msg, key=f"project.readme.{field}") return None - content_type_raw = readme.get('content-type') + content_type_raw = readme.get("content-type") if content_type_raw is not None: content_type = self.ensure_str( - content_type_raw, 'project.readme.content-type' + content_type_raw, "project.readme.content-type" ) if content_type is None: return None - filename_raw = readme.get('file') + filename_raw = readme.get("file") if filename_raw is not None: - filename = self.ensure_str(filename_raw, 'project.readme.file') + filename = self.ensure_str(filename_raw, "project.readme.file") if filename is None: return None - text_raw = readme.get('text') + text_raw = readme.get("text") if text_raw is not None: - text = self.ensure_str(text_raw, 'project.readme.text') + text = self.ensure_str(text_raw, "project.readme.text") if text is None: return None if (filename and text) or (not filename and not text): msg = f'Invalid "project.readme" value, expecting either "file" or "text" (got "{readme}")' - self.config_error(msg, key='project.readme') + self.config_error(msg, key="project.readme") return None if not content_type: msg = 'Field "project.readme.content-type" missing' - self.config_error(msg, key='project.readme.content-type') + self.config_error(msg, key="project.readme.content-type") return None else: msg = ( f'Field "project.readme" has an invalid type, expecting either, ' f'a string or dictionary of strings (got "{readme}")' ) - self.config_error(msg, key='project.readme') + self.config_error(msg, key="project.readme") return None if filename: file = project_dir.joinpath(filename) if not file.is_file(): msg = f'Readme file not found ("{filename}")' - self.config_error(msg, key='project.readme.file') + self.config_error(msg, key="project.readme.file") return None - text = file.read_text(encoding='utf-8') + text = file.read_text(encoding="utf-8") assert text is not None return Readme(text, file, content_type) def get_dependencies(self, project: ProjectTable) -> list[Requirement]: requirement_strings: list[str] | None = None - requirement_strings_raw = project.get('dependencies') + requirement_strings_raw = project.get("dependencies") if requirement_strings_raw is not None: requirement_strings = self.ensure_list( - requirement_strings_raw, 'project.dependencies' + requirement_strings_raw, "project.dependencies" ) if requirement_strings is None: return [] @@ -248,7 +247,7 @@ def get_dependencies(self, project: ProjectTable) -> list[Requirement]: 'Field "project.dependencies" contains an invalid PEP 508 ' f'requirement string "{req}" ("{e}")' ) - self.config_error(msg, key='project.dependencies') + self.config_error(msg, key="project.dependencies") return [] return requirements @@ -256,7 +255,7 @@ def get_optional_dependencies( self, project: ProjectTable, ) -> dict[str, list[Requirement]]: - val = project.get('optional-dependencies') + val = project.get("optional-dependencies") if not val: return {} @@ -266,7 +265,7 @@ def get_optional_dependencies( 'Field "project.optional-dependencies" has an invalid type, expecting a ' f'dictionary of PEP 508 requirement strings (got "{val}")' ) - self.config_error(msg, key='project.optional-dependencies') + self.config_error(msg, key="project.optional-dependencies") return {} for extra, requirements in val.copy().items(): assert isinstance(extra, str) @@ -275,7 +274,7 @@ def get_optional_dependencies( f'Field "project.optional-dependencies.{extra}" has an invalid type, expecting a ' f'dictionary PEP 508 requirement strings (got "{requirements}")' ) - self.config_error(msg, key=f'project.optional-dependencies.{extra}') + self.config_error(msg, key=f"project.optional-dependencies.{extra}") return {} requirements_dict[extra] = [] for req in requirements: @@ -284,7 +283,7 @@ def get_optional_dependencies( f'Field "project.optional-dependencies.{extra}" has an invalid type, ' f'expecting a PEP 508 requirement string (got "{req}")' ) - self.config_error(msg, key=f'project.optional-dependencies.{extra}') + self.config_error(msg, key=f"project.optional-dependencies.{extra}") return {} try: requirements_dict[extra].append( @@ -295,12 +294,12 @@ def get_optional_dependencies( f'Field "project.optional-dependencies.{extra}" contains ' f'an invalid PEP 508 requirement string "{req}" ("{e}")' ) - self.config_error(msg, key=f'project.optional-dependencies.{extra}') + self.config_error(msg, key=f"project.optional-dependencies.{extra}") return {} return dict(requirements_dict) def get_entrypoints(self, project: ProjectTable) -> dict[str, dict[str, str]]: - val = project.get('entry-points', None) + val = project.get("entry-points", None) if val is None: return {} if not isinstance(val, dict): @@ -308,23 +307,23 @@ def get_entrypoints(self, project: ProjectTable) -> dict[str, dict[str, str]]: 'Field "project.entry-points" has an invalid type, expecting a ' f'dictionary of entrypoint sections (got "{val}")' ) - self.config_error(msg, key='project.entry-points') + self.config_error(msg, key="project.entry-points") return {} for section, entrypoints in val.items(): assert isinstance(section, str) - if not re.match(r'^\w+(\.\w+)*$', section): + if not re.match(r"^\w+(\.\w+)*$", section): msg = ( 'Field "project.entry-points" has an invalid value, expecting a name ' f'containing only alphanumeric, underscore, or dot characters (got "{section}")' ) - self.config_error(msg, key='project.entry-points') + self.config_error(msg, key="project.entry-points") return {} if not isinstance(entrypoints, dict): msg = ( f'Field "project.entry-points.{section}" has an invalid type, expecting a ' f'dictionary of entrypoints (got "{entrypoints}")' ) - self.config_error(msg, key=f'project.entry-points.{section}') + self.config_error(msg, key=f"project.entry-points.{section}") return {} for name, entrypoint in entrypoints.items(): assert isinstance(name, str) @@ -333,18 +332,18 @@ def get_entrypoints(self, project: ProjectTable) -> dict[str, dict[str, str]]: f'Field "project.entry-points.{section}.{name}" has an invalid type, ' f'expecting a string (got "{entrypoint}")' ) - self.config_error(msg, key=f'project.entry-points.{section}.{name}') + self.config_error(msg, key=f"project.entry-points.{section}.{name}") return {} return val def get_dynamic(self, project: ProjectTable) -> list[str]: - dynamic: list[str] = project.get('dynamic', []) # type: ignore[assignment] + dynamic: list[str] = project.get("dynamic", []) # type: ignore[assignment] - self.ensure_list(dynamic, 'project.dynamic') + self.ensure_list(dynamic, "project.dynamic") - if 'name' in dynamic: + if "name" in dynamic: msg = 'Unsupported field "name" in "project.dynamic"' - self.config_error(msg, key='project.dynamic') + self.config_error(msg, key="project.dynamic") return [] return dynamic @@ -353,7 +352,7 @@ def _get_files_from_globs( self, project_dir: pathlib.Path, globs: Iterable[str] ) -> Generator[pathlib.Path, None, None]: for glob in globs: - if glob.startswith(('..', '/')): + if glob.startswith(("..", "/")): msg = f'"{glob}" is an invalid "project.license-files" glob: the pattern must match files within the project directory' self.config_error(msg) break diff --git a/tests/test_internals.py b/tests/test_internals.py index eb17e87..a19544e 100644 --- a/tests/test_internals.py +++ b/tests/test_internals.py @@ -9,15 +9,15 @@ def test_all() -> None: - assert 'typing' not in dir(pyproject_metadata) - assert 'annotations' not in dir(pyproject_metadata.constants) - assert 'annotations' not in dir(pyproject_metadata.errors) - assert 'annotations' not in dir(pyproject_metadata.pyproject) + assert "typing" not in dir(pyproject_metadata) + assert "annotations" not in dir(pyproject_metadata.constants) + assert "annotations" not in dir(pyproject_metadata.errors) + assert "annotations" not in dir(pyproject_metadata.pyproject) def test_project_table_all() -> None: if sys.version_info < (3, 11): - pytest.importorskip('typing_extensions') + pytest.importorskip("typing_extensions") import pyproject_metadata.project_table - assert 'annotations' not in dir(pyproject_metadata.project_table) + assert "annotations" not in dir(pyproject_metadata.project_table) diff --git a/tests/test_rfc822.py b/tests/test_rfc822.py index 6b22320..dabdc20 100644 --- a/tests/test_rfc822.py +++ b/tests/test_rfc822.py @@ -14,84 +14,84 @@ @pytest.mark.parametrize( - ('items', 'data'), + ("items", "data"), [ pytest.param( [], - '', - id='empty', + "", + id="empty", ), pytest.param( [ - ('Foo', 'Bar'), + ("Foo", "Bar"), ], - 'Foo: Bar\n', - id='simple', + "Foo: Bar\n", + id="simple", ), pytest.param( [ - ('Foo', 'Bar'), - ('Foo2', 'Bar2'), + ("Foo", "Bar"), + ("Foo2", "Bar2"), ], """\ Foo: Bar Foo2: Bar2 """, - id='multiple', + id="multiple", ), pytest.param( [ - ('Foo', 'Unicøde'), + ("Foo", "Unicøde"), ], - 'Foo: Unicøde\n', - id='unicode', + "Foo: Unicøde\n", + id="unicode", ), pytest.param( [ - ('Foo', '🕵️'), + ("Foo", "🕵️"), ], - 'Foo: 🕵️\n', - id='emoji', + "Foo: 🕵️\n", + id="emoji", ), pytest.param( [ - ('Item', None), + ("Item", None), ], - '', - id='none', + "", + id="none", ), pytest.param( [ - ('ItemA', 'ValueA'), - ('ItemB', 'ValueB'), - ('ItemC', 'ValueC'), + ("ItemA", "ValueA"), + ("ItemB", "ValueB"), + ("ItemC", "ValueC"), ], """\ ItemA: ValueA ItemB: ValueB ItemC: ValueC """, - id='order 1', + id="order 1", ), pytest.param( [ - ('ItemB', 'ValueB'), - ('ItemC', 'ValueC'), - ('ItemA', 'ValueA'), + ("ItemB", "ValueB"), + ("ItemC", "ValueC"), + ("ItemA", "ValueA"), ], """\ ItemB: ValueB ItemC: ValueC ItemA: ValueA """, - id='order 2', + id="order 2", ), pytest.param( [ - ('ItemA', 'ValueA1'), - ('ItemB', 'ValueB'), - ('ItemC', 'ValueC'), - ('ItemA', 'ValueA2'), + ("ItemA", "ValueA1"), + ("ItemB", "ValueB"), + ("ItemC", "ValueC"), + ("ItemA", "ValueA2"), ], """\ ItemA: ValueA1 @@ -99,13 +99,13 @@ ItemC: ValueC ItemA: ValueA2 """, - id='multiple keys', + id="multiple keys", ), pytest.param( [ - ('ItemA', 'ValueA'), - ('ItemB', 'ValueB1\nValueB2\nValueB3'), - ('ItemC', 'ValueC'), + ("ItemA", "ValueA"), + ("ItemB", "ValueB1\nValueB2\nValueB3"), + ("ItemC", "ValueC"), ], """\ ItemA: ValueA @@ -114,7 +114,7 @@ ValueB3 ItemC: ValueC """, - id='multiline', + id="multiline", ), ], ) @@ -126,33 +126,33 @@ def test_headers( monkeypatch.setattr( pyproject_metadata.constants, - 'KNOWN_METADATA_FIELDS', + "KNOWN_METADATA_FIELDS", {x.lower() for x, _ in items}, ) for name, value in items: smart_message[name] = value - data = textwrap.dedent(data) + '\n' + data = textwrap.dedent(data) + "\n" assert str(message) == data assert bytes(message) == data.encode() assert email.message_from_string(str(message)).items() == [ - (a, '\n '.join(b.splitlines())) for a, b in items if b is not None + (a, "\n ".join(b.splitlines())) for a, b in items if b is not None ] def test_body(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr( pyproject_metadata.constants, - 'KNOWN_METADATA_FIELDS', - {'itema', 'itemb', 'itemc'}, + "KNOWN_METADATA_FIELDS", + {"itema", "itemb", "itemc"}, ) message = pyproject_metadata.RFC822Message() - message['ItemA'] = 'ValueA' - message['ItemB'] = 'ValueB' - message['ItemC'] = 'ValueC' + message["ItemA"] = "ValueA" + message["ItemB"] = "ValueB" + message["ItemC"] = "ValueC" body = inspect.cleandoc(""" Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris congue semper fermentum. Nunc vitae tempor ante. Aenean aliquet posuere lacus non faucibus. @@ -172,7 +172,7 @@ def test_body(monkeypatch: pytest.MonkeyPatch) -> None: ItemB: ValueB ItemC: ValueC """) - full = f'{headers}\n\n{body}' + full = f"{headers}\n\n{body}" message.set_payload(textwrap.dedent(body)) @@ -182,7 +182,7 @@ def test_body(monkeypatch: pytest.MonkeyPatch) -> None: assert new_message.items() == message.items() assert new_message.get_payload() == message.get_payload() - assert bytes(message) == full.encode('utf-8') + assert bytes(message) == full.encode("utf-8") def test_unknown_field() -> None: @@ -191,23 +191,23 @@ def test_unknown_field() -> None: pyproject_metadata.ConfigurationError, match=re.escape('Unknown field "Unknown"'), ): - message['Unknown'] = 'Value' + message["Unknown"] = "Value" def test_known_field() -> None: message = pyproject_metadata.RFC822Message() - message['Platform'] = 'Value' - assert str(message) == 'Platform: Value\n\n' + message["Platform"] = "Value" + assert str(message) == "Platform: Value\n\n" def test_convert_optional_dependencies() -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '0.1.0', - 'optional-dependencies': { - 'test': [ + "project": { + "name": "example", + "version": "0.1.0", + "optional-dependencies": { + "test": [ 'foo; os_name == "nt" or sys_platform == "win32"', 'bar; os_name == "posix" and sys_platform == "linux"', ], @@ -216,7 +216,7 @@ def test_convert_optional_dependencies() -> None: } ) message = metadata.as_rfc822() - requires = message.get_all('Requires-Dist') + requires = message.get_all("Requires-Dist") assert requires == [ 'foo; (os_name == "nt" or sys_platform == "win32") and extra == "test"', 'bar; os_name == "posix" and sys_platform == "linux" and extra == "test"', @@ -226,24 +226,24 @@ def test_convert_optional_dependencies() -> None: def test_convert_author_email() -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '0.1.0', - 'authors': [ + "project": { + "name": "example", + "version": "0.1.0", + "authors": [ { - 'name': 'John Doe, Inc.', - 'email': 'johndoe@example.com', + "name": "John Doe, Inc.", + "email": "johndoe@example.com", }, { - 'name': 'Kate Doe, LLC.', - 'email': 'katedoe@example.com', + "name": "Kate Doe, LLC.", + "email": "katedoe@example.com", }, ], }, } ) message = metadata.as_rfc822() - assert message.get_all('Author-Email') == [ + assert message.get_all("Author-Email") == [ '"John Doe, Inc." , "Kate Doe, LLC." ' ] @@ -251,16 +251,16 @@ def test_convert_author_email() -> None: def test_long_version() -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '0.0.0+super.duper.long.version.string.that.is.longer.than.sixty.seven.characters', + "project": { + "name": "example", + "version": "0.0.0+super.duper.long.version.string.that.is.longer.than.sixty.seven.characters", } } ) message = metadata.as_rfc822() assert ( - message.get('Version') - == '0.0.0+super.duper.long.version.string.that.is.longer.than.sixty.seven.characters' + message.get("Version") + == "0.0.0+super.duper.long.version.string.that.is.longer.than.sixty.seven.characters" ) assert ( bytes(message) @@ -268,8 +268,8 @@ def test_long_version() -> None: Metadata-Version: 2.1 Name: example Version: 0.0.0+super.duper.long.version.string.that.is.longer.than.sixty.seven.characters - """).encode('utf-8') - + b'\n\n' + """).encode("utf-8") + + b"\n\n" ) assert ( str(message) @@ -278,5 +278,5 @@ def test_long_version() -> None: Name: example Version: 0.0.0+super.duper.long.version.string.that.is.longer.than.sixty.seven.characters """) - + '\n\n' + + "\n\n" ) diff --git a/tests/test_standard_metadata.py b/tests/test_standard_metadata.py index 9866251..66a2855 100644 --- a/tests/test_standard_metadata.py +++ b/tests/test_standard_metadata.py @@ -13,7 +13,6 @@ import packaging.version import pytest - if sys.version_info < (3, 11): import tomli as tomllib else: @@ -21,7 +20,6 @@ import pyproject_metadata - DIR = pathlib.Path(__file__).parent.resolve() @@ -31,25 +29,25 @@ exceptiongroup = None # type: ignore[assignment] -@pytest.fixture(params=['one_error', 'all_errors', 'exceptiongroup']) +@pytest.fixture(params=["one_error", "all_errors", "exceptiongroup"]) def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) -> bool: param: str = request.param - if param == 'exceptiongroup': + if param == "exceptiongroup": if exceptiongroup is None: - pytest.skip('exceptiongroup is not installed') + pytest.skip("exceptiongroup is not installed") monkeypatch.setattr( - pyproject_metadata.errors, 'ExceptionGroup', exceptiongroup.ExceptionGroup + pyproject_metadata.errors, "ExceptionGroup", exceptiongroup.ExceptionGroup ) - return param != 'one_error' + return param != "one_error" @pytest.mark.parametrize( - ('data', 'error'), + ("data", "error"), [ pytest.param( - '', + "", 'Section "project" missing in pyproject.toml', - id='Missing project section', + id="Missing project section", ), pytest.param( """ @@ -58,7 +56,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) version = '0.1.0' """, 'Field "project.name" has an invalid type, expecting a string (got "True")', - id='Invalid name type', + id="Invalid name type", ), pytest.param( """ @@ -68,7 +66,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) not-real-key = true """, 'Extra keys present in "project": "not-real-key"', - id='Invalid project key', + id="Invalid project key", ), pytest.param( """ @@ -80,7 +78,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) ] """, 'Unsupported field "name" in "project.dynamic"', - id='Unsupported field in project.dynamic', + id="Unsupported field in project.dynamic", ), pytest.param( """ @@ -92,7 +90,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) ] """, 'Field "project.dynamic" contains item with invalid type, expecting a string (got "3")', - id='Unsupported type in project.dynamic', + id="Unsupported type in project.dynamic", ), pytest.param( """ @@ -101,7 +99,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) version = true """, 'Field "project.version" has an invalid type, expecting a string (got "True")', - id='Invalid version type', + id="Invalid version type", ), pytest.param( """ @@ -109,7 +107,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) name = 'test' """, 'Field "project.version" missing and "version" not specified in "project.dynamic"', - id='Missing version', + id="Missing version", ), pytest.param( """ @@ -118,7 +116,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) version = '0.1.0-extra' """, 'Invalid "project.version" value, expecting a valid PEP 440 version (got "0.1.0-extra")', - id='Invalid version value', + id="Invalid version value", ), pytest.param( """ @@ -128,7 +126,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license = true """, 'Field "project.license" has an invalid type, expecting a string or dictionary of strings (got "True")', - id='License invalid type', + id="License invalid type", ), pytest.param( """ @@ -138,7 +136,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license = {} """, 'Invalid "project.license" value, expecting either "file" or "text" (got "{}")', - id='Missing license keys', + id="Missing license keys", ), pytest.param( """ @@ -151,7 +149,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Invalid "project.license" value, expecting either "file" ' "or \"text\" (got \"{'file': '...', 'text': '...'}\")" ), - id='Both keys for license', + id="Both keys for license", ), pytest.param( """ @@ -161,7 +159,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license = { made-up = ':(' } """, 'Unexpected field "project.license.made-up"', - id='Got made-up license field', + id="Got made-up license field", ), pytest.param( """ @@ -171,7 +169,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license = { file = true } """, 'Field "project.license.file" has an invalid type, expecting a string (got "True")', - id='Invalid type for license.file', + id="Invalid type for license.file", ), pytest.param( """ @@ -181,7 +179,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license = { text = true } """, 'Field "project.license.text" has an invalid type, expecting a string (got "True")', - id='Invalid type for license.text', + id="Invalid type for license.text", ), pytest.param( """ @@ -191,7 +189,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license = { file = 'this-file-does-not-exist' } """, 'License file not found ("this-file-does-not-exist")', - id='License file not present', + id="License file not present", ), pytest.param( """ @@ -204,7 +202,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.readme" has an invalid type, expecting either, ' 'a string or dictionary of strings (got "True")' ), - id='Invalid readme type', + id="Invalid readme type", ), pytest.param( """ @@ -214,7 +212,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = {} """, 'Invalid "project.readme" value, expecting either "file" or "text" (got "{}")', - id='Empty readme table', + id="Empty readme table", ), pytest.param( """ @@ -224,7 +222,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = 'README.jpg' """, 'Could not infer content type for readme file "README.jpg"', - id='Unsupported filename in readme', + id="Unsupported filename in readme", ), pytest.param( """ @@ -237,7 +235,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Invalid "project.readme" value, expecting either "file" or ' "\"text\" (got \"{'file': '...', 'text': '...'}\")" ), - id='Both readme fields', + id="Both readme fields", ), pytest.param( """ @@ -247,7 +245,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { made-up = ':(' } """, 'Unexpected field "project.readme.made-up"', - id='Unexpected field in readme', + id="Unexpected field in readme", ), pytest.param( """ @@ -257,7 +255,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { file = true } """, 'Field "project.readme.file" has an invalid type, expecting a string (got "True")', - id='Invalid type for readme.file', + id="Invalid type for readme.file", ), pytest.param( """ @@ -267,7 +265,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { text = true } """, 'Field "project.readme.text" has an invalid type, expecting a string (got "True")', - id='Invalid type for readme.text', + id="Invalid type for readme.text", ), pytest.param( """ @@ -277,7 +275,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { file = 'this-file-does-not-exist', content-type = '...' } """, 'Readme file not found ("this-file-does-not-exist")', - id='Readme file not present', + id="Readme file not present", ), pytest.param( """ @@ -287,7 +285,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { file = 'README.md' } """, 'Field "project.readme.content-type" missing', - id='Missing content-type for readme', + id="Missing content-type for readme", ), pytest.param( """ @@ -297,7 +295,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { file = "README.md", content-type = true } """, 'Field "project.readme.content-type" has an invalid type, expecting a string (got "True")', - id='Wrong content-type type for readme', + id="Wrong content-type type for readme", ), pytest.param( """ @@ -307,7 +305,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) readme = { text = '...' } """, 'Field "project.readme.content-type" missing', - id='Missing content-type for readme', + id="Missing content-type for readme", ), pytest.param( """ @@ -317,7 +315,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) description = true """, 'Field "project.description" has an invalid type, expecting a string (got "True")', - id='Invalid description type', + id="Invalid description type", ), pytest.param( """ @@ -327,7 +325,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) dependencies = 'some string!' """, 'Field "project.dependencies" has an invalid type, expecting a list of strings (got "some string!")', - id='Invalid dependencies type', + id="Invalid dependencies type", ), pytest.param( """ @@ -339,7 +337,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) ] """, 'Field "project.dependencies" contains item with invalid type, expecting a string (got "99")', - id='Invalid dependencies item type', + id="Invalid dependencies item type", ), pytest.param( """ @@ -354,7 +352,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.dependencies" contains an invalid PEP 508 requirement ' 'string "definitely not a valid PEP 508 requirement!" ' ), - id='Invalid dependencies item', + id="Invalid dependencies item", ), pytest.param( """ @@ -367,7 +365,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.optional-dependencies" has an invalid type, ' 'expecting a dictionary of PEP 508 requirement strings (got "True")' ), - id='Invalid optional-dependencies type', + id="Invalid optional-dependencies type", ), pytest.param( """ @@ -381,7 +379,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.optional-dependencies.test" has an invalid type, ' 'expecting a dictionary PEP 508 requirement strings (got "some string!")' ), - id='Invalid optional-dependencies not list', + id="Invalid optional-dependencies not list", ), pytest.param( """ @@ -397,7 +395,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.optional-dependencies.test" has an invalid type, ' 'expecting a PEP 508 requirement string (got "True")' ), - id='Invalid optional-dependencies item type', + id="Invalid optional-dependencies item type", ), pytest.param( """ @@ -413,7 +411,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.optional-dependencies.test" contains an invalid ' 'PEP 508 requirement string "definitely not a valid PEP 508 requirement!" ' ), - id='Invalid optional-dependencies item', + id="Invalid optional-dependencies item", ), pytest.param( """ @@ -423,7 +421,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) requires-python = true """, 'Field "project.requires-python" has an invalid type, expecting a string (got "True")', - id='Invalid requires-python type', + id="Invalid requires-python type", ), pytest.param( """ @@ -433,7 +431,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) requires-python = '3.8' """, 'Invalid "project.requires-python" value, expecting a valid specifier set (got "3.8")', - id='Invalid requires-python value', + id="Invalid requires-python value", ), pytest.param( """ @@ -443,7 +441,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) keywords = 'some string!' """, 'Field "project.keywords" has an invalid type, expecting a list of strings (got "some string!")', - id='Invalid keywords type', + id="Invalid keywords type", ), pytest.param( """ @@ -455,7 +453,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) ] """, 'Field "project.keywords" contains item with invalid type, expecting a string (got "True")', - id='Invalid keywords item type', + id="Invalid keywords item type", ), pytest.param( """ @@ -468,7 +466,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.authors" has an invalid type, expecting a list of ' 'dictionaries containing the "name" and/or "email" keys (got "{}")' ), - id='Invalid authors type', + id="Invalid authors type", ), pytest.param( """ @@ -483,7 +481,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.authors" has an invalid type, expecting a list of ' 'dictionaries containing the "name" and/or "email" keys (got "[True]")' ), - id='Invalid authors item type', + id="Invalid authors item type", ), pytest.param( """ @@ -496,7 +494,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.maintainers" has an invalid type, expecting a list of ' 'dictionaries containing the "name" and/or "email" keys (got "{}")' ), - id='Invalid maintainers type', + id="Invalid maintainers type", ), pytest.param( """ @@ -511,7 +509,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.maintainers" has an invalid type, expecting a list of ' 'dictionaries containing the "name" and/or "email" keys (got "[10]")' ), - id='Invalid maintainers item type', + id="Invalid maintainers item type", ), pytest.param( """ @@ -521,7 +519,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) classifiers = 'some string!' """, 'Field "project.classifiers" has an invalid type, expecting a list of strings (got "some string!")', - id='Invalid classifiers type', + id="Invalid classifiers type", ), pytest.param( """ @@ -533,7 +531,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) ] """, 'Field "project.classifiers" contains item with invalid type, expecting a string (got "True")', - id='Invalid classifiers item type', + id="Invalid classifiers item type", ), pytest.param( """ @@ -544,7 +542,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) homepage = true """, 'Field "project.urls.homepage" has an invalid type, expecting a string (got "True")', - id='Invalid urls homepage type', + id="Invalid urls homepage type", ), pytest.param( """ @@ -555,7 +553,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) documentation = true """, 'Field "project.urls.documentation" has an invalid type, expecting a string (got "True")', - id='Invalid urls documentation type', + id="Invalid urls documentation type", ), pytest.param( """ @@ -566,7 +564,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) repository = true """, 'Field "project.urls.repository" has an invalid type, expecting a string (got "True")', - id='Invalid urls repository type', + id="Invalid urls repository type", ), pytest.param( """ @@ -577,7 +575,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) changelog = true """, 'Field "project.urls.changelog" has an invalid type, expecting a string (got "True")', - id='Invalid urls changelog type', + id="Invalid urls changelog type", ), pytest.param( """ @@ -587,7 +585,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) scripts = [] """, 'Field "project.scripts" has an invalid type, expecting a dictionary of strings (got "[]")', - id='Invalid scripts type', + id="Invalid scripts type", ), pytest.param( """ @@ -597,7 +595,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) gui-scripts = [] """, 'Field "project.gui-scripts" has an invalid type, expecting a dictionary of strings (got "[]")', - id='Invalid gui-scripts type', + id="Invalid gui-scripts type", ), pytest.param( """ @@ -610,7 +608,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.entry-points" has an invalid type, ' 'expecting a dictionary of entrypoint sections (got "[]")' ), - id='Invalid entry-points type', + id="Invalid entry-points type", ), pytest.param( """ @@ -623,7 +621,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.entry-points.section" has an invalid type, ' 'expecting a dictionary of entrypoints (got "something")' ), - id='Invalid entry-points section type', + id="Invalid entry-points section type", ), pytest.param( """ @@ -634,7 +632,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) entrypoint = [] """, 'Field "project.entry-points.section.entrypoint" has an invalid type, expecting a string (got "[]")', - id='Invalid entry-points entrypoint type', + id="Invalid entry-points entrypoint type", ), pytest.param( """ @@ -644,9 +642,9 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) """, ( 'Invalid project name ".test". A valid name consists only of ASCII letters and ' - 'numbers, period, underscore and hyphen. It must start and end with a letter or number' + "numbers, period, underscore and hyphen. It must start and end with a letter or number" ), - id='Invalid project name', + id="Invalid project name", ), pytest.param( """ @@ -659,7 +657,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) 'Field "project.entry-points" has an invalid value, expecting a name containing only ' 'alphanumeric, underscore, or dot characters (got "bad-name")' ), - id='Invalid entry-points name', + id="Invalid entry-points name", ), # both license files and classic license are not allowed pytest.param( @@ -671,7 +669,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license.text = "stuff" """, '"project.license-files" must not be used when "project.license" is not a SPDX license expression', - id='Both license files and classic license', + id="Both license files and classic license", ), pytest.param( """ @@ -681,7 +679,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license-files = ["../LICENSE"] """, '"../LICENSE" is an invalid "project.license-files" glob: the pattern must match files within the project directory', - id='Parent license-files glob', + id="Parent license-files glob", ), pytest.param( """ @@ -691,7 +689,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license-files = [12] """, 'Field "project.license-files" contains item with invalid type, expecting a string (got "12")', - id='Parent license-files invalid type', + id="Parent license-files invalid type", ), pytest.param( """ @@ -701,7 +699,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license-files = ["this", 12] """, 'Field "project.license-files" contains item with invalid type, expecting a string (got "12")', - id='Parent license-files invalid type', + id="Parent license-files invalid type", ), pytest.param( """ @@ -711,7 +709,7 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) license-files = ["/LICENSE"] """, '"/LICENSE" is an invalid "project.license-files" glob: the pattern must match files within the project directory', - id='Aboslute license-files glob', + id="Aboslute license-files glob", ), pytest.param( """ @@ -722,14 +720,14 @@ def all_errors(request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch) classifiers = ["License :: OSI Approved :: MIT License"] """, 'Setting "project.license" to an SPDX license expression is not compatible with "License ::" classifiers', - id='SPDX license and License trove classifiers', + id="SPDX license and License trove classifiers", ), ], ) def test_load( data: str, error: str, monkeypatch: pytest.MonkeyPatch, all_errors: bool ) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') + monkeypatch.chdir(DIR / "packages/full-metadata") if not all_errors: with pytest.raises( pyproject_metadata.ConfigurationError, match=re.escape(error) @@ -741,7 +739,7 @@ def test_load( else: with warnings.catch_warnings(): warnings.simplefilter( - action='ignore', category=pyproject_metadata.ConfigurationWarning + action="ignore", category=pyproject_metadata.ConfigurationWarning ) with pytest.raises(pyproject_metadata.errors.ExceptionGroup) as execinfo: pyproject_metadata.StandardMetadata.from_pyproject( @@ -753,19 +751,19 @@ def test_load( args = [e.args[0] for e in exceptions] assert len(args) == 1 assert error in args[0] - assert 'Failed to parse pyproject.toml' in repr(execinfo.value) + assert "Failed to parse pyproject.toml" in repr(execinfo.value) @pytest.mark.parametrize( - ('data', 'errors'), + ("data", "errors"), [ pytest.param( - '[project]', + "[project]", [ 'Field "project.name" missing', 'Field "project.version" missing and "version" not specified in "project.dynamic"', ], - id='Missing project name', + id="Missing project name", ), pytest.param( """ @@ -780,7 +778,7 @@ def test_load( 'Unsupported field "name" in "project.dynamic"', 'Field "project.name" has an invalid type, expecting a string (got "True")', ], - id='Unsupported field in project.dynamic', + id="Unsupported field in project.dynamic", ), pytest.param( """ @@ -795,7 +793,7 @@ def test_load( 'Field "project.dynamic" contains item with invalid type, expecting a string (got "3")', 'Field "project.name" has an invalid type, expecting a string (got "True")', ], - id='Unsupported type in project.dynamic', + id="Unsupported type in project.dynamic", ), pytest.param( """ @@ -809,7 +807,7 @@ def test_load( 'Field "project.license-files" contains item with invalid type, expecting a string (got "12")', 'Could not infer content type for readme file "README.jpg"', ], - id='Unsupported filename in readme', + id="Unsupported filename in readme", ), pytest.param( """ @@ -828,14 +826,14 @@ def test_load( 'Could not infer content type for readme file "README.jpg"', 'Field "project.entry-points" has an invalid value, expecting a name containing only alphanumeric, underscore, or dot characters (got "bad-name")', ], - id='Four errors including extra keys', + id="Four errors including extra keys", ), ], ) def test_load_multierror( data: str, errors: list[str], monkeypatch: pytest.MonkeyPatch, all_errors: bool ) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') + monkeypatch.chdir(DIR / "packages/full-metadata") if not all_errors: with pytest.raises( pyproject_metadata.ConfigurationError, match=re.escape(errors[0]) @@ -847,7 +845,7 @@ def test_load_multierror( else: with warnings.catch_warnings(): warnings.simplefilter( - action='ignore', category=pyproject_metadata.ConfigurationWarning + action="ignore", category=pyproject_metadata.ConfigurationWarning ) with pytest.raises(pyproject_metadata.errors.ExceptionGroup) as execinfo: pyproject_metadata.StandardMetadata.from_pyproject( @@ -859,11 +857,11 @@ def test_load_multierror( args = [e.args[0] for e in exceptions] assert len(args) == len(errors) assert args == errors - assert 'Failed to parse pyproject.toml' in repr(execinfo.value) + assert "Failed to parse pyproject.toml" in repr(execinfo.value) @pytest.mark.parametrize( - ('data', 'error', 'metadata_version'), + ("data", "error", "metadata_version"), [ pytest.param( """ @@ -873,8 +871,8 @@ def test_load_multierror( license = "MIT" """, 'Setting "project.license" to an SPDX license expression is supported only when emitting metadata version >= 2.4', - '2.3', - id='SPDX with metadata_version 2.3', + "2.3", + id="SPDX with metadata_version 2.3", ), pytest.param( """ @@ -884,15 +882,15 @@ def test_load_multierror( license-files = ["README.md"] """, '"project.license-files" is supported only when emitting metadata version >= 2.4', - '2.3', - id='license-files with metadata_version 2.3', + "2.3", + id="license-files with metadata_version 2.3", ), ], ) def test_load_with_metadata_version( data: str, error: str, metadata_version: str, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') + monkeypatch.chdir(DIR / "packages/full-metadata") with pytest.raises(pyproject_metadata.ConfigurationError, match=re.escape(error)): pyproject_metadata.StandardMetadata.from_pyproject( tomllib.loads(textwrap.dedent(data)), metadata_version=metadata_version @@ -900,7 +898,7 @@ def test_load_with_metadata_version( @pytest.mark.parametrize( - ('data', 'error', 'metadata_version'), + ("data", "error", "metadata_version"), [ pytest.param( """ @@ -910,8 +908,8 @@ def test_load_with_metadata_version( license.text = "MIT" """, 'Set "project.license" to an SPDX license expression for metadata >= 2.4', - '2.4', - id='Classic license with metadata 2.4', + "2.4", + id="Classic license with metadata 2.4", ), pytest.param( """ @@ -921,109 +919,109 @@ def test_load_with_metadata_version( classifiers = ["License :: OSI Approved :: MIT License"] """, '"License ::" classifiers are deprecated for metadata >= 2.4, use a SPDX license expression for "project.license" instead', - '2.4', - id='License trove classfiers with metadata 2.4', + "2.4", + id="License trove classfiers with metadata 2.4", ), ], ) def test_load_with_metadata_version_warnings( data: str, error: str, metadata_version: str, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') + monkeypatch.chdir(DIR / "packages/full-metadata") with pytest.warns(pyproject_metadata.ConfigurationWarning, match=re.escape(error)): pyproject_metadata.StandardMetadata.from_pyproject( tomllib.loads(textwrap.dedent(data)), metadata_version=metadata_version ) -@pytest.mark.parametrize('after_rfc', [False, True]) +@pytest.mark.parametrize("after_rfc", [False, True]) def test_value(after_rfc: bool, monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') - with open('pyproject.toml', 'rb') as f: + monkeypatch.chdir(DIR / "packages/full-metadata") + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) if after_rfc: metadata.as_rfc822() assert metadata.dynamic == [] - assert metadata.name == 'full_metadata' - assert metadata.canonical_name == 'full-metadata' - assert metadata.version == packaging.version.Version('3.2.1') - assert metadata.requires_python == packaging.specifiers.Specifier('>=3.8') + assert metadata.name == "full_metadata" + assert metadata.canonical_name == "full-metadata" + assert metadata.version == packaging.version.Version("3.2.1") + assert metadata.requires_python == packaging.specifiers.Specifier(">=3.8") assert isinstance(metadata.license, pyproject_metadata.License) assert metadata.license.file is None - assert metadata.license.text == 'some license text' + assert metadata.license.text == "some license text" assert isinstance(metadata.readme, pyproject_metadata.Readme) - assert metadata.readme.file == pathlib.Path('README.md') - assert metadata.readme.text == pathlib.Path('README.md').read_text(encoding='utf-8') - assert metadata.readme.content_type == 'text/markdown' - assert metadata.description == 'A package with all the metadata :)' + assert metadata.readme.file == pathlib.Path("README.md") + assert metadata.readme.text == pathlib.Path("README.md").read_text(encoding="utf-8") + assert metadata.readme.content_type == "text/markdown" + assert metadata.description == "A package with all the metadata :)" assert metadata.authors == [ - ('Unknown', 'example@example.com'), - ('Example!', None), + ("Unknown", "example@example.com"), + ("Example!", None), ] assert metadata.maintainers == [ - ('Other Example', 'other@example.com'), + ("Other Example", "other@example.com"), ] - assert metadata.keywords == ['trampolim', 'is', 'interesting'] + assert metadata.keywords == ["trampolim", "is", "interesting"] assert metadata.classifiers == [ - 'Development Status :: 4 - Beta', - 'Programming Language :: Python', + "Development Status :: 4 - Beta", + "Programming Language :: Python", ] assert metadata.urls == { - 'changelog': 'github.com/some/repo/blob/master/CHANGELOG.rst', - 'documentation': 'readthedocs.org', - 'homepage': 'example.com', - 'repository': 'github.com/some/repo', + "changelog": "github.com/some/repo/blob/master/CHANGELOG.rst", + "documentation": "readthedocs.org", + "homepage": "example.com", + "repository": "github.com/some/repo", } assert metadata.entrypoints == { - 'custom': { - 'full-metadata': 'full_metadata:main_custom', + "custom": { + "full-metadata": "full_metadata:main_custom", }, } assert metadata.scripts == { - 'full-metadata': 'full_metadata:main_cli', + "full-metadata": "full_metadata:main_cli", } assert metadata.gui_scripts == { - 'full-metadata-gui': 'full_metadata:main_gui', + "full-metadata-gui": "full_metadata:main_gui", } assert list(map(str, metadata.dependencies)) == [ - 'dependency1', - 'dependency2>1.0.0', - 'dependency3[extra]', + "dependency1", + "dependency2>1.0.0", + "dependency3[extra]", 'dependency4; os_name != "nt"', 'dependency5[other-extra]>1.0; os_name == "nt"', ] - assert list(metadata.optional_dependencies.keys()) == ['test'] - assert list(map(str, metadata.optional_dependencies['test'])) == [ - 'test_dependency', - 'test_dependency[test_extra]', + assert list(metadata.optional_dependencies.keys()) == ["test"] + assert list(map(str, metadata.optional_dependencies["test"])) == [ + "test_dependency", + "test_dependency[test_extra]", 'test_dependency[test_extra2]>3.0; os_name == "nt"', ] def test_read_license(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata2') - with open('pyproject.toml', 'rb') as f: + monkeypatch.chdir(DIR / "packages/full-metadata2") + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) assert isinstance(metadata.license, pyproject_metadata.License) - assert metadata.license.file == pathlib.Path('LICENSE') - assert metadata.license.text == 'Some license! 👋\n' + assert metadata.license.file == pathlib.Path("LICENSE") + assert metadata.license.text == "Some license! 👋\n" @pytest.mark.parametrize( - ('package', 'content_type'), + ("package", "content_type"), [ - ('full-metadata', 'text/markdown'), - ('full-metadata2', 'text/x-rst'), + ("full-metadata", "text/markdown"), + ("full-metadata2", "text/x-rst"), ], ) def test_readme_content_type( package: str, content_type: str, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.chdir(DIR / 'packages' / package) - with open('pyproject.toml', 'rb') as f: + monkeypatch.chdir(DIR / "packages" / package) + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) assert isinstance(metadata.readme, pyproject_metadata.Readme) @@ -1031,137 +1029,137 @@ def test_readme_content_type( def test_readme_content_type_unknown(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/unknown-readme-type') + monkeypatch.chdir(DIR / "packages/unknown-readme-type") with pytest.raises( pyproject_metadata.ConfigurationError, match=re.escape( 'Could not infer content type for readme file "README.just-made-this-up-now"' ), - ), open('pyproject.toml', 'rb') as f: + ), open("pyproject.toml", "rb") as f: pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) def test_as_json(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') + monkeypatch.chdir(DIR / "packages/full-metadata") - with open('pyproject.toml', 'rb') as f: + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) core_metadata = metadata.as_json() assert core_metadata == { - 'author': 'Example!', - 'author_email': 'Unknown ', - 'classifier': [ - 'Development Status :: 4 - Beta', - 'Programming Language :: Python', + "author": "Example!", + "author_email": "Unknown ", + "classifier": [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", ], - 'description': 'some readme 👋\n', - 'description_content_type': 'text/markdown', - 'home_page': 'example.com', - 'keywords': ['trampolim', 'is', 'interesting'], - 'license': 'some license text', - 'maintainer_email': 'Other Example ', - 'metadata_version': '2.1', - 'name': 'full_metadata', - 'project_url': [ - 'Homepage, example.com', - 'Documentation, readthedocs.org', - 'Repository, github.com/some/repo', - 'Changelog, github.com/some/repo/blob/master/CHANGELOG.rst', + "description": "some readme 👋\n", + "description_content_type": "text/markdown", + "home_page": "example.com", + "keywords": ["trampolim", "is", "interesting"], + "license": "some license text", + "maintainer_email": "Other Example ", + "metadata_version": "2.1", + "name": "full_metadata", + "project_url": [ + "Homepage, example.com", + "Documentation, readthedocs.org", + "Repository, github.com/some/repo", + "Changelog, github.com/some/repo/blob/master/CHANGELOG.rst", ], - 'provides_extra': ['test'], - 'requires_dist': [ - 'dependency1', - 'dependency2>1.0.0', - 'dependency3[extra]', + "provides_extra": ["test"], + "requires_dist": [ + "dependency1", + "dependency2>1.0.0", + "dependency3[extra]", 'dependency4; os_name != "nt"', 'dependency5[other-extra]>1.0; os_name == "nt"', 'test_dependency; extra == "test"', 'test_dependency[test_extra]; extra == "test"', 'test_dependency[test_extra2]>3.0; os_name == "nt" and ' 'extra == "test"', ], - 'requires_python': '>=3.8', - 'summary': 'A package with all the metadata :)', - 'version': '3.2.1', + "requires_python": ">=3.8", + "summary": "A package with all the metadata :)", + "version": "3.2.1", } def test_as_rfc822(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/full-metadata') + monkeypatch.chdir(DIR / "packages/full-metadata") - with open('pyproject.toml', 'rb') as f: + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) core_metadata = metadata.as_rfc822() assert core_metadata.items() == [ - ('Metadata-Version', '2.1'), - ('Name', 'full_metadata'), - ('Version', '3.2.1'), - ('Summary', 'A package with all the metadata :)'), - ('Keywords', 'trampolim,is,interesting'), - ('Home-page', 'example.com'), - ('Author', 'Example!'), - ('Author-Email', 'Unknown '), - ('Maintainer-Email', 'Other Example '), - ('License', 'some license text'), - ('Classifier', 'Development Status :: 4 - Beta'), - ('Classifier', 'Programming Language :: Python'), - ('Project-URL', 'Homepage, example.com'), - ('Project-URL', 'Documentation, readthedocs.org'), - ('Project-URL', 'Repository, github.com/some/repo'), - ('Project-URL', 'Changelog, github.com/some/repo/blob/master/CHANGELOG.rst'), - ('Requires-Python', '>=3.8'), - ('Requires-Dist', 'dependency1'), - ('Requires-Dist', 'dependency2>1.0.0'), - ('Requires-Dist', 'dependency3[extra]'), - ('Requires-Dist', 'dependency4; os_name != "nt"'), - ('Requires-Dist', 'dependency5[other-extra]>1.0; os_name == "nt"'), - ('Provides-Extra', 'test'), - ('Requires-Dist', 'test_dependency; extra == "test"'), - ('Requires-Dist', 'test_dependency[test_extra]; extra == "test"'), + ("Metadata-Version", "2.1"), + ("Name", "full_metadata"), + ("Version", "3.2.1"), + ("Summary", "A package with all the metadata :)"), + ("Keywords", "trampolim,is,interesting"), + ("Home-page", "example.com"), + ("Author", "Example!"), + ("Author-Email", "Unknown "), + ("Maintainer-Email", "Other Example "), + ("License", "some license text"), + ("Classifier", "Development Status :: 4 - Beta"), + ("Classifier", "Programming Language :: Python"), + ("Project-URL", "Homepage, example.com"), + ("Project-URL", "Documentation, readthedocs.org"), + ("Project-URL", "Repository, github.com/some/repo"), + ("Project-URL", "Changelog, github.com/some/repo/blob/master/CHANGELOG.rst"), + ("Requires-Python", ">=3.8"), + ("Requires-Dist", "dependency1"), + ("Requires-Dist", "dependency2>1.0.0"), + ("Requires-Dist", "dependency3[extra]"), + ("Requires-Dist", 'dependency4; os_name != "nt"'), + ("Requires-Dist", 'dependency5[other-extra]>1.0; os_name == "nt"'), + ("Provides-Extra", "test"), + ("Requires-Dist", 'test_dependency; extra == "test"'), + ("Requires-Dist", 'test_dependency[test_extra]; extra == "test"'), ( - 'Requires-Dist', + "Requires-Dist", 'test_dependency[test_extra2]>3.0; os_name == "nt" and extra == "test"', ), - ('Description-Content-Type', 'text/markdown'), + ("Description-Content-Type", "text/markdown"), ] - assert core_metadata.get_payload() == 'some readme 👋\n' + assert core_metadata.get_payload() == "some readme 👋\n" def test_as_json_spdx(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/spdx') + monkeypatch.chdir(DIR / "packages/spdx") - with open('pyproject.toml', 'rb') as f: + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) core_metadata = metadata.as_json() assert core_metadata == { - 'license_expression': 'MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)', - 'license_file': [ - 'AUTHORS.txt', - 'LICENSE.md', - 'LICENSE.txt', - 'licenses/LICENSE.MIT', + "license_expression": "MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)", + "license_file": [ + "AUTHORS.txt", + "LICENSE.md", + "LICENSE.txt", + "licenses/LICENSE.MIT", ], - 'metadata_version': '2.4', - 'name': 'example', - 'version': '1.2.3', + "metadata_version": "2.4", + "name": "example", + "version": "1.2.3", } def test_as_rfc822_spdx(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/spdx') + monkeypatch.chdir(DIR / "packages/spdx") - with open('pyproject.toml', 'rb') as f: + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) core_metadata = metadata.as_rfc822() assert core_metadata.items() == [ - ('Metadata-Version', '2.4'), - ('Name', 'example'), - ('Version', '1.2.3'), - ('License-Expression', 'MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)'), - ('License-File', 'AUTHORS.txt'), - ('License-File', 'LICENSE.md'), - ('License-File', 'LICENSE.txt'), - ('License-File', 'licenses/LICENSE.MIT'), + ("Metadata-Version", "2.4"), + ("Name", "example"), + ("Version", "1.2.3"), + ("License-Expression", "MIT OR GPL-2.0-or-later OR (FSFUL AND BSD-2-Clause)"), + ("License-File", "AUTHORS.txt"), + ("License-File", "LICENSE.md"), + ("License-File", "LICENSE.txt"), + ("License-File", "licenses/LICENSE.MIT"), ] assert core_metadata.get_payload() is None @@ -1170,13 +1168,13 @@ def test_as_rfc822_spdx(monkeypatch: pytest.MonkeyPatch) -> None: def test_as_rfc822_spdx_empty_glob( monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path, all_errors: bool ) -> None: - shutil.copytree(DIR / 'packages/spdx', tmp_path / 'spdx') - monkeypatch.chdir(tmp_path / 'spdx') + shutil.copytree(DIR / "packages/spdx", tmp_path / "spdx") + monkeypatch.chdir(tmp_path / "spdx") - pathlib.Path('AUTHORS.txt').unlink() + pathlib.Path("AUTHORS.txt").unlink() msg = 'Every pattern in "project.license-files" must match at least one file: "AUTHORS*" did not match any' - with open('pyproject.toml', 'rb') as f: + with open("pyproject.toml", "rb") as f: if all_errors: with pytest.raises( pyproject_metadata.errors.ExceptionGroup, @@ -1184,7 +1182,7 @@ def test_as_rfc822_spdx_empty_glob( pyproject_metadata.StandardMetadata.from_pyproject( tomllib.load(f), all_errors=all_errors ) - assert 'Failed to parse pyproject.toml' in str(execinfo.value) + assert "Failed to parse pyproject.toml" in str(execinfo.value) assert [msg] == [str(e) for e in execinfo.value.exceptions] else: with pytest.raises( @@ -1197,32 +1195,32 @@ def test_as_rfc822_spdx_empty_glob( def test_as_rfc822_dynamic(monkeypatch: pytest.MonkeyPatch) -> None: - monkeypatch.chdir(DIR / 'packages/dynamic-description') + monkeypatch.chdir(DIR / "packages/dynamic-description") - with open('pyproject.toml', 'rb') as f: + with open("pyproject.toml", "rb") as f: metadata = pyproject_metadata.StandardMetadata.from_pyproject(tomllib.load(f)) - metadata.dynamic_metadata = ['description'] + metadata.dynamic_metadata = ["description"] core_metadata = metadata.as_rfc822() assert core_metadata.items() == [ - ('Metadata-Version', '2.2'), - ('Name', 'dynamic-description'), - ('Version', '1.0.0'), - ('Dynamic', 'description'), + ("Metadata-Version", "2.2"), + ("Name", "dynamic-description"), + ("Version", "1.0.0"), + ("Dynamic", "description"), ] -@pytest.mark.parametrize('metadata_version', ['2.1', '2.2', '2.3']) +@pytest.mark.parametrize("metadata_version", ["2.1", "2.2", "2.3"]) def test_as_rfc822_set_metadata(metadata_version: str) -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'hi', - 'version': '1.2', - 'optional-dependencies': { - 'under_score': ['some_package'], - 'da-sh': ['some-package'], - 'do.t': ['some.package'], - 'empty': [], + "project": { + "name": "hi", + "version": "1.2", + "optional-dependencies": { + "under_score": ["some_package"], + "da-sh": ["some-package"], + "do.t": ["some.package"], + "empty": [], }, } }, @@ -1230,14 +1228,14 @@ def test_as_rfc822_set_metadata(metadata_version: str) -> None: ) assert metadata.metadata_version == metadata_version - rfc822 = bytes(metadata.as_rfc822()).decode('utf-8') + rfc822 = bytes(metadata.as_rfc822()).decode("utf-8") - assert f'Metadata-Version: {metadata_version}' in rfc822 + assert f"Metadata-Version: {metadata_version}" in rfc822 - assert 'Provides-Extra: under-score' in rfc822 - assert 'Provides-Extra: da-sh' in rfc822 - assert 'Provides-Extra: do-t' in rfc822 - assert 'Provides-Extra: empty' in rfc822 + assert "Provides-Extra: under-score" in rfc822 + assert "Provides-Extra: da-sh" in rfc822 + assert "Provides-Extra: do-t" in rfc822 + assert "Provides-Extra: empty" in rfc822 assert 'Requires-Dist: some_package; extra == "under-score"' in rfc822 assert 'Requires-Dist: some-package; extra == "da-sh"' in rfc822 assert 'Requires-Dist: some.package; extra == "do-t"' in rfc822 @@ -1246,96 +1244,96 @@ def test_as_rfc822_set_metadata(metadata_version: str) -> None: def test_as_json_set_metadata() -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'hi', - 'version': '1.2', - 'optional-dependencies': { - 'under_score': ['some_package'], - 'da-sh': ['some-package'], - 'do.t': ['some.package'], - 'empty': [], + "project": { + "name": "hi", + "version": "1.2", + "optional-dependencies": { + "under_score": ["some_package"], + "da-sh": ["some-package"], + "do.t": ["some.package"], + "empty": [], }, } }, - metadata_version='2.1', + metadata_version="2.1", ) - assert metadata.metadata_version == '2.1' + assert metadata.metadata_version == "2.1" json = metadata.as_json() assert json == { - 'metadata_version': '2.1', - 'name': 'hi', - 'provides_extra': ['under-score', 'da-sh', 'do-t', 'empty'], - 'requires_dist': [ + "metadata_version": "2.1", + "name": "hi", + "provides_extra": ["under-score", "da-sh", "do-t", "empty"], + "requires_dist": [ 'some_package; extra == "under-score"', 'some-package; extra == "da-sh"', 'some.package; extra == "do-t"', ], - 'version': '1.2', + "version": "1.2", } def test_as_rfc822_set_metadata_invalid() -> None: with pytest.raises( pyproject_metadata.ConfigurationError, - match='The metadata_version must be one of', + match="The metadata_version must be one of", ) as err: pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'hi', - 'version': '1.2', + "project": { + "name": "hi", + "version": "1.2", }, }, - metadata_version='2.0', + metadata_version="2.0", ) - assert '2.1' in str(err.value) - assert '2.2' in str(err.value) - assert '2.3' in str(err.value) + assert "2.1" in str(err.value) + assert "2.2" in str(err.value) + assert "2.3" in str(err.value) def test_as_rfc822_invalid_dynamic() -> None: metadata = pyproject_metadata.StandardMetadata( - name='something', - version=packaging.version.Version('1.0.0'), - dynamic_metadata=['name'], + name="something", + version=packaging.version.Version("1.0.0"), + dynamic_metadata=["name"], ) with pytest.raises( pyproject_metadata.ConfigurationError, - match='Field cannot be set as dynamic metadata: name', + match="Field cannot be set as dynamic metadata: name", ): metadata.as_rfc822() - metadata.dynamic_metadata = ['version'] + metadata.dynamic_metadata = ["version"] with pytest.raises( pyproject_metadata.ConfigurationError, - match='Field cannot be set as dynamic metadata: version', + match="Field cannot be set as dynamic metadata: version", ): metadata.as_rfc822() - metadata.dynamic_metadata = ['unknown'] + metadata.dynamic_metadata = ["unknown"] with pytest.raises( pyproject_metadata.ConfigurationError, - match='Field is not known: unknown', + match="Field is not known: unknown", ): metadata.as_rfc822() def test_as_rfc822_mapped_dynamic() -> None: metadata = pyproject_metadata.StandardMetadata( - name='something', - version=packaging.version.Version('1.0.0'), - dynamic_metadata=list(pyproject_metadata.field_to_metadata('description')), + name="something", + version=packaging.version.Version("1.0.0"), + dynamic_metadata=list(pyproject_metadata.field_to_metadata("description")), ) assert ( str(metadata.as_rfc822()) - == 'Metadata-Version: 2.2\nName: something\nVersion: 1.0.0\nDynamic: Summary\n\n' + == "Metadata-Version: 2.2\nName: something\nVersion: 1.0.0\nDynamic: Summary\n\n" ) def test_as_rfc822_missing_version() -> None: - metadata = pyproject_metadata.StandardMetadata(name='something') + metadata = pyproject_metadata.StandardMetadata(name="something") with pytest.raises( - pyproject_metadata.ConfigurationError, match='Missing version field' + pyproject_metadata.ConfigurationError, match="Missing version field" ): metadata.as_rfc822() @@ -1347,11 +1345,11 @@ def test_stically_defined_dynamic_field() -> None: ): pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '1.2.3', - 'dynamic': [ - 'version', + "project": { + "name": "example", + "version": "1.2.3", + "dynamic": [ + "version", ], }, } @@ -1359,21 +1357,21 @@ def test_stically_defined_dynamic_field() -> None: @pytest.mark.parametrize( - 'value', + "value", [ - '<3.10', - '>3.7,<3.11', - '>3.7,<3.11,!=3.8.4', - '~=3.10,!=3.10.3', + "<3.10", + ">3.7,<3.11", + ">3.7,<3.11,!=3.8.4", + "~=3.10,!=3.10.3", ], ) def test_requires_python(value: str) -> None: pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '0.1.0', - 'requires-python': value, + "project": { + "name": "example", + "version": "0.1.0", + "requires-python": value, }, } ) @@ -1382,34 +1380,34 @@ def test_requires_python(value: str) -> None: def test_version_dynamic() -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'dynamic': [ - 'version', + "project": { + "name": "example", + "dynamic": [ + "version", ], }, } ) - metadata.version = packaging.version.Version('1.2.3') + metadata.version = packaging.version.Version("1.2.3") def test_modify_dynamic() -> None: metadata = pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '1.2.3', - 'dynamic': [ - 'requires-python', + "project": { + "name": "example", + "version": "1.2.3", + "dynamic": [ + "requires-python", ], }, } ) - metadata.requires_python = packaging.specifiers.SpecifierSet('>=3.12') + metadata.requires_python = packaging.specifiers.SpecifierSet(">=3.12") with pytest.raises( AttributeError, match=re.escape('Field "version" is not dynamic') ): - metadata.version = packaging.version.Version('1.2.3') + metadata.version = packaging.version.Version("1.2.3") def test_missing_keys_warns() -> None: @@ -1419,10 +1417,10 @@ def test_missing_keys_warns() -> None: ): pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '1.2.3', - 'not-real-key': True, + "project": { + "name": "example", + "version": "1.2.3", + "not-real-key": True, }, } ) @@ -1431,7 +1429,7 @@ def test_missing_keys_warns() -> None: def test_missing_keys_okay() -> None: pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': {'name': 'example', 'version': '1.2.3', 'not-real-key': True}, + "project": {"name": "example", "version": "1.2.3", "not-real-key": True}, }, allow_extra_keys=True, ) @@ -1440,15 +1438,15 @@ def test_missing_keys_okay() -> None: def test_extra_top_level() -> None: assert not pyproject_metadata.extras_top_level( { - 'project': {}, + "project": {}, } ) - assert {'also-not-real', 'not-real'} == pyproject_metadata.extras_top_level( + assert {"also-not-real", "not-real"} == pyproject_metadata.extras_top_level( { - 'not-real': {}, - 'also-not-real': {}, - 'project': {}, - 'build-system': {}, + "not-real": {}, + "also-not-real": {}, + "project": {}, + "build-system": {}, } ) @@ -1456,18 +1454,18 @@ def test_extra_top_level() -> None: def test_extra_build_system() -> None: assert not pyproject_metadata.extras_build_system( { - 'build-system': { - 'build-backend': 'one', - 'requires': ['two'], - 'backend-path': 'local', + "build-system": { + "build-backend": "one", + "requires": ["two"], + "backend-path": "local", }, } ) - assert {'also-not-real', 'not-real'} == pyproject_metadata.extras_build_system( + assert {"also-not-real", "not-real"} == pyproject_metadata.extras_build_system( { - 'build-system': { - 'not-real': {}, - 'also-not-real': {}, + "build-system": { + "not-real": {}, + "also-not-real": {}, } } ) @@ -1482,10 +1480,10 @@ def test_multiline_description_warns() -> None: ): pyproject_metadata.StandardMetadata.from_pyproject( { - 'project': { - 'name': 'example', - 'version': '1.2.3', - 'description': 'this\nis multiline', + "project": { + "name": "example", + "version": "1.2.3", + "description": "this\nis multiline", }, } )