from __future__ import annotations import importlib import shutil import subprocess import sys import tarfile import zipfile from dataclasses import dataclass from enum import Enum from pathlib import Path, PurePosixPath import pytest from packaging.specifiers import SpecifierSet from packaging.utils import canonicalize_name, parse_wheel_filename from packaging.version import Version import dlp_io try: import tomllib except ModuleNotFoundError: # Python 3.9-3.10 import tomli as tomllib PROJECT_ROOT = Path(__file__).resolve().parents[1] class DynamicField(Enum): VERSION = "version" class PackageName(Enum): DLP_IO = canonicalize_name("dlp-io") class PackageClassifier(Enum): WINDOWS = "Operating System :: Microsoft :: Windows" @dataclass(frozen=True) class BuiltDistributions: wheel: Path sdist: Path def _load_pyproject() -> dict: with (PROJECT_ROOT / "pyproject.toml").open("rb") as file: return tomllib.load(file) @pytest.fixture(scope="module") def built_distributions(tmp_path_factory: pytest.TempPathFactory) -> BuiltDistributions: root = tmp_path_factory.mktemp("dlp-io-build") source = root / "source" source.mkdir() shutil.copy2(PROJECT_ROOT / "pyproject.toml", source) shutil.copy2(PROJECT_ROOT / "MANIFEST.in", source) shutil.copytree(PROJECT_ROOT / "dlp_io", source / "dlp_io") shutil.copytree(PROJECT_ROOT / "docs", source / "docs") dist = root / "dist" completed = subprocess.run( [ sys.executable, "-m", "build", "--no-isolation", "--outdir", str(dist), ], cwd=source, check=False, capture_output=True, ) assert completed.returncode == 0 wheels = tuple(dist.glob("*.whl")) sdists = tuple(dist.glob("*.tar.gz")) assert len(wheels) == 1 assert len(sdists) == 1 return BuiltDistributions(wheel=wheels[0], sdist=sdists[0]) def test_pyproject_declares_structured_public_package_contract() -> None: config = _load_pyproject() project = config["project"] setuptools = config["tool"]["setuptools"] assert PackageName(canonicalize_name(project["name"])) is PackageName.DLP_IO assert SpecifierSet(project["requires-python"]) == SpecifierSet(">=3.9") assert {DynamicField(item) for item in project["dynamic"]} == { DynamicField.VERSION } known_classifier_values = {item.value for item in PackageClassifier} known_classifiers = { PackageClassifier(item) for item in project["classifiers"] if item in known_classifier_values } assert PackageClassifier.WINDOWS in known_classifiers assert {Path(item) for item in setuptools["packages"]} == {Path("dlp_io")} assert {Path(item) for item in setuptools["package-data"]["dlp_io"]} == { Path("py.typed") } version_ref = config["tool"]["setuptools"]["dynamic"]["version"]["attr"] module_name, attribute_name = version_ref.rsplit(".", 1) declared_version = getattr(importlib.import_module(module_name), attribute_name) assert Version(declared_version) == Version(dlp_io.__version__) def test_wheel_contains_only_installable_dlp_io_runtime( built_distributions: BuiltDistributions, ) -> None: name, version, build, tags = parse_wheel_filename(built_distributions.wheel.name) with zipfile.ZipFile(built_distributions.wheel) as archive: members = {PurePosixPath(item) for item in archive.namelist()} assert PackageName(name) is PackageName.DLP_IO assert version == Version(dlp_io.__version__) assert build == () assert tags assert PurePosixPath("dlp_io/__init__.py") in members assert PurePosixPath("dlp_io/py.typed") in members member_roots = {Path(item.parts[0]) for item in members} assert member_roots.isdisjoint({Path("tests"), Path("Lib")}) assert PurePosixPath(".env") not in members def test_sdist_contains_package_sources_and_public_readme_only( built_distributions: BuiltDistributions, ) -> None: with tarfile.open(built_distributions.sdist, mode="r:gz") as archive: members = { PurePosixPath(*PurePosixPath(item.name).parts[1:]) for item in archive.getmembers() if item.isfile() } assert PurePosixPath("pyproject.toml") in members assert PurePosixPath("docs/DLP_IO_LIBRARY.md") in members assert PurePosixPath("dlp_io/__init__.py") in members member_roots = {Path(item.parts[0]) for item in members} assert Path("tests") not in member_roots assert PurePosixPath("README.md") not in members assert PurePosixPath(".env") not in members