Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/pr_code_changes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@ jobs:
uses: astral-sh/setup-uv@v8.1.0
- name: Check formatting
run: uvx ruff format --check .
BundleMetadataContract:
name: Validate bundle metadata contract
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
- name: Install package
run: uv pip install --system .
- name: Install bundle validation tooling
# Pin the test-only bundle contract dependency until policyengine-bundles
# has published releases suitable for ordinary dependency specifiers.
run: uv pip install --system "policyengine-bundles @ git+https://github.com/PolicyEngine/policyengine-bundles@8ae9f56fefcf89f69b8a7e3bc49928509c6207be"
- name: Validate runtime metadata contract
run: python -m pytest policyengine_uk/tests/test_build_metadata.py
Test:
runs-on: macos-latest
permissions:
Expand Down
1 change: 1 addition & 0 deletions changelog.d/add-bundle-runtime-metadata.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added runtime metadata with installed policyengine-core identity for bundle validation.
16 changes: 10 additions & 6 deletions policyengine_uk/build_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from pathlib import Path
import subprocess

from policyengine_core import get_runtime_metadata as get_core_runtime_metadata

PACKAGE_NAME = "policyengine-uk"
PACKAGE_ROOT = Path(__file__).resolve().parent
DATA_BUILD_SURFACE = (
Expand Down Expand Up @@ -39,11 +41,8 @@ def _iter_surface_files() -> list[Path]:
return files


def _get_package_version() -> str | None:
try:
return metadata.version(PACKAGE_NAME)
except metadata.PackageNotFoundError:
return None
def _get_package_version() -> str:
return metadata.version(PACKAGE_NAME)


def _get_git_sha() -> str | None:
Expand Down Expand Up @@ -73,10 +72,15 @@ def get_data_build_fingerprint() -> str:
return f"sha256:{digest.hexdigest()}"


def get_data_build_metadata() -> dict[str, str | None]:
def get_runtime_metadata() -> dict[str, object]:
return {
"name": PACKAGE_NAME,
"version": _get_package_version(),
"git_sha": _get_git_sha(),
"data_build_fingerprint": get_data_build_fingerprint(),
"core": get_core_runtime_metadata(),
}


def get_data_build_metadata() -> dict[str, object]:
return get_runtime_metadata()
89 changes: 58 additions & 31 deletions policyengine_uk/tests/test_build_metadata.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
from contextlib import ExitStack
import importlib.util
from pathlib import Path
import sys
from unittest.mock import patch

from policyengine_uk.build_metadata import (
get_data_build_fingerprint,
get_data_build_metadata,
import pytest

BUILD_METADATA_PATH = Path(__file__).resolve().parents[1] / "build_metadata.py"
SPEC = importlib.util.spec_from_file_location(
"policyengine_uk_build_metadata_under_test",
BUILD_METADATA_PATH,
)
build_metadata = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = build_metadata
SPEC.loader.exec_module(build_metadata)

get_data_build_fingerprint = build_metadata.get_data_build_fingerprint
get_data_build_metadata = build_metadata.get_data_build_metadata
get_runtime_metadata = build_metadata.get_runtime_metadata


def test_data_build_fingerprint_is_stable_within_process():
Expand All @@ -17,33 +29,48 @@ def test_data_build_fingerprint_is_stable_within_process():
assert first == second


def test_get_data_build_metadata_includes_version_git_sha_and_fingerprint():
def test_get_runtime_metadata_includes_required_bundle_fields(monkeypatch):
get_data_build_fingerprint.cache_clear()

with ExitStack() as stack:
stack.enter_context(
patch(
"policyengine_uk.build_metadata._get_package_version",
return_value="2.74.0",
)
)
stack.enter_context(
patch(
"policyengine_uk.build_metadata._get_git_sha",
return_value="deadbeef",
)
)
stack.enter_context(
patch(
"policyengine_uk.build_metadata.get_data_build_fingerprint",
return_value="sha256:fingerprint",
)
)
metadata = get_data_build_metadata()

assert metadata == {
"name": "policyengine-uk",
"version": "2.74.0",
"git_sha": "deadbeef",
"data_build_fingerprint": "sha256:fingerprint",
monkeypatch.setattr(build_metadata, "_get_package_version", lambda: "2.74.0")
monkeypatch.setattr(build_metadata, "_get_git_sha", lambda: "deadbeef")
monkeypatch.setattr(
build_metadata,
"get_data_build_fingerprint",
lambda: "sha256:fingerprint",
)
monkeypatch.setattr(
build_metadata,
"get_core_runtime_metadata",
lambda: {
"name": "policyengine-core",
"version": "3.26.0",
"git_sha": "coredeadbeef",
},
)

metadata = get_runtime_metadata()

assert metadata["name"] == "policyengine-uk"
assert metadata["version"] == "2.74.0"
assert metadata["git_sha"] == "deadbeef"
assert metadata["data_build_fingerprint"] == "sha256:fingerprint"
assert metadata["core"] == {
"name": "policyengine-core",
"version": "3.26.0",
"git_sha": "coredeadbeef",
}


def test_get_data_build_metadata_uses_runtime_metadata():
with patch(
f"{SPEC.name}.get_runtime_metadata",
return_value={"name": "policyengine-uk"},
):
assert get_data_build_metadata() == {"name": "policyengine-uk"}


def test_runtime_metadata_uses_bundle_contract_when_available():
policyengine_bundles = pytest.importorskip("policyengine_bundles")

policyengine_bundles.load_component_metadata(get_runtime_metadata())
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ classifiers = [
]
requires-python = ">=3.9"
dependencies = [
"policyengine-core>=3.25.0",
"policyengine-core>=3.26.0",
"microdf-python>=1.2.1",
"pydantic>=2.11.7",
"tables>=3.9.2,<3.10.2; python_version < '3.10'",
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.