Skip to content

Commit

Permalink
Merge pull request #11 from sbidoul/simplify
Browse files Browse the repository at this point in the history
Simplify, don't use pyproject_metadata dependency
  • Loading branch information
sbidoul authored Sep 23, 2023
2 parents 100e4df + 36f6308 commit fb21a82
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 59 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ name = "pyproject-dependencies"
dependencies = [
"build",
"packaging",
"pyproject-metadata",
"tomli ; python_version<'3.11'",
"typing_extensions ; python_version<'3.8'",
]
Expand Down
105 changes: 53 additions & 52 deletions src/pyproject_dependencies/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
import re
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any, List, Mapping, Optional, Sequence, TypeVar, Union
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union

from build.util import project_wheel_metadata
from packaging.requirements import Requirement
from packaging.utils import canonicalize_name
from packaging.version import Version
from pyproject_metadata import RFC822Message, StandardMetadata
from packaging.utils import NormalizedName, canonicalize_name

from .compat import Protocol, tomllib
from .compat import tomllib

extra_marker_re = re.compile(r"extra\s*==")

Expand Down Expand Up @@ -45,60 +44,66 @@ def subprocess_runner(
raise subprocess.CalledProcessError(res.returncode, cmd)


_T = TypeVar("_T")
class InvalidPyproject(RuntimeError):
pass


class BasicPackageMetadata(Protocol):
"""A subset of the importlib.metadata.PackageMetadata protocol."""
def _validate_pyproject(project: Dict[str, Any]) -> None:
if "name" not in project:
raise InvalidPyproject("Missing 'name' key")
dynamic = project.get("dynamic", [])
if "dependencies" in dynamic and "dependencies" in project:
raise InvalidPyproject(
"'dependencies' key cannot be set while declared as 'dynamic'"
)

def __getitem__(self, key: str) -> str:
... # pragma: no cover

def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
"""
Return all values associated with a possibly multi-valued key.
"""
@dataclass
class BasicPackageMetadata:
name: NormalizedName
dependencies: List[str]


class RFC822MessageAdapter(BasicPackageMetadata):
def __init__(self, rfc822_message: RFC822Message):
self._rfc822_message = rfc822_message

def __getitem__(self, key: str) -> str:
value = self._rfc822_message.headers[key]
if len(value) > 1:
raise KeyError(f"multiple values for key {key!r}")
return value[0]

def get_all(self, name: str, failobj: _T) -> Union[List[Any], _T]:
return self._rfc822_message.headers.get(name, failobj)


def pyproject_metadata(
def metadata_from_pyproject(
project_path: Path,
) -> Optional[BasicPackageMetadata]:
) -> Union[BasicPackageMetadata, None]:
"""Obtain metadata for a project using pyproject.toml.
Return None if the dependencies are not static or if the project does not use
pyproject.toml.
"""
pyproject_path = project_path / "pyproject.toml"
if not pyproject_path.is_file():
# no pyproject.toml, need to use the default build backend
return None
parsed_pyproject = tomllib.loads(pyproject_path.read_text(encoding="utf-8"))
if "project" not in parsed_pyproject:
project = parsed_pyproject.get("project")
if not project:
# no project table, need to use build backend
return None
metadata = StandardMetadata.from_pyproject(parsed_pyproject)
if (
"dependencies" in metadata.dynamic
or "optional-dependencies" in metadata.dynamic
):
_validate_pyproject(project)
if "dependencies" in project.get("dynamic", []):
# dynamic dependencies, need to use build backend
return None
if not metadata.version:
# Fill-in metadata.version because it cannot be dynamic when converting
# to rfc822 format. We don't use it as we are only interested in Requires-Dist.
metadata.version = Version("0")
return RFC822MessageAdapter(metadata.as_rfc822())
return BasicPackageMetadata(
name=canonicalize_name(project["name"]),
dependencies=project.get("dependencies", []),
)


def metadata_from_build_backend(
project_path: Path, isolated: bool
) -> BasicPackageMetadata:
metadata = project_wheel_metadata(
project_path,
isolated,
runner=subprocess_runner,
)
requires_dist = metadata.get_all("Requires-Dist", [])
dependencies = [dep for dep in requires_dist if not _dep_has_extra(dep)]
return BasicPackageMetadata(
name=canonicalize_name(metadata["Name"]), dependencies=dependencies
)


def main() -> None:
Expand Down Expand Up @@ -155,16 +160,15 @@ def main() -> None:
name_filters_re = []
for name_filter in args.name_filters or []:
name_filters_re.append(re.compile(name_filter))
# ask the build backend to prepare metadata for each projects
# obtain the metadata we need for each projects
metadata_by_project_name = {}
for project_path in project_paths:
try:
project_metadata = pyproject_metadata(
project_metadata = metadata_from_pyproject(
project_path
) or project_wheel_metadata(
) or metadata_from_build_backend(
project_path,
not args.no_isolation,
runner=subprocess_runner,
)
except Exception as e:
if args.ignore_build_errors:
Expand All @@ -179,21 +183,18 @@ def main() -> None:
file=sys.stderr,
)
sys.exit(1)
project_name = canonicalize_name(project_metadata["Name"])
if project_name in metadata_by_project_name:
if project_metadata.name in metadata_by_project_name:
print(
f"Warning: {project_name} already seen, "
f"Warning: {project_metadata.name} already seen, "
f"ignoring {project_path.resolve()}",
file=sys.stderr,
)
continue
metadata_by_project_name[project_name] = project_metadata
metadata_by_project_name[project_metadata.name] = project_metadata
# filter
deps = set()
for project_metadata in metadata_by_project_name.values():
for dep in project_metadata.get_all("Requires-Dist", []):
if _dep_has_extra(dep):
continue
for dep in project_metadata.dependencies:
req = Requirement(dep)
req_name = canonicalize_name(req.name)
if not args.no_exclude_self and req_name in metadata_by_project_name:
Expand Down
7 changes: 1 addition & 6 deletions src/pyproject_dependencies/compat.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import sys

if sys.version_info < (3, 8):
from typing_extensions import Protocol
else:
from typing import Protocol

if sys.version_info < (3, 11):
import tomli as tomllib
else:
import tomllib


__all__ = ["tomllib", "Protocol"]
__all__ = ["tomllib"]

0 comments on commit fb21a82

Please sign in to comment.