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
1 change: 1 addition & 0 deletions changelog.d/offline-import.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Country model imports now work without network access when the bundled release manifest already certifies the installed country package version. Hugging Face release-manifest transport failures fall back to bundled data certification only when the runtime model version and data build fingerprint gates still match.
23 changes: 17 additions & 6 deletions src/policyengine/provenance/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,17 @@ def get_data_release_manifest(country_id: str) -> DataReleaseManifest:
if token:
headers["Authorization"] = f"Bearer {token}"

response = requests.get(
https_release_manifest_uri(country_manifest.data_package),
headers=headers,
timeout=HF_REQUEST_TIMEOUT_SECONDS,
)
try:
response = requests.get(
https_release_manifest_uri(country_manifest.data_package),
headers=headers,
timeout=HF_REQUEST_TIMEOUT_SECONDS,
)
except requests.RequestException as exc:
raise DataReleaseManifestUnavailableError(
"Could not fetch the data release manifest from Hugging Face."
) from exc

if response.status_code in (401, 403):
raise DataReleaseManifestUnavailableError(
"Could not fetch the data release manifest from Hugging Face. "
Expand All @@ -250,7 +256,12 @@ def get_data_release_manifest(country_id: str) -> DataReleaseManifest:
raise DataReleaseManifestUnavailableError(
"No data release manifest was published for this data package."
)
response.raise_for_status()
try:
response.raise_for_status()
except requests.RequestException as exc:
raise DataReleaseManifestUnavailableError(
"Could not fetch the data release manifest from Hugging Face."
) from exc
return DataReleaseManifest.model_validate_json(response.text)


Expand Down
73 changes: 68 additions & 5 deletions tests/test_release_manifests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""Tests for bundled compatibility manifests and data release manifests."""

import json
import os
import re
import subprocess
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

Expand Down Expand Up @@ -316,6 +319,22 @@ def test__given_private_manifest_unavailable__then_bundled_certification_is_used

assert certification == get_release_manifest("us").certification

def test__given_manifest_request_timeout__then_bundled_certification_is_used(
self,
):
get_data_release_manifest.cache_clear()

with patch(
"policyengine.provenance.manifest.requests.get",
side_effect=Timeout("network timeout"),
):
certification = certify_data_release_compatibility(
"us",
runtime_model_version="1.687.0",
)

assert certification == get_release_manifest("us").certification

def test__given_private_manifest_unavailable_and_fingerprint_mismatch__then_fails(
self,
):
Expand Down Expand Up @@ -348,24 +367,68 @@ def test__given_private_manifest_unavailable_and_fingerprint_mismatch__then_fail
else:
raise AssertionError("Expected fingerprint mismatch to fail")

def test__given_manifest_fetch_failure__then_certification_does_not_fallback(
def test__given_manifest_fetch_failure_and_version_mismatch__then_fallback_fails(
self,
):
get_data_release_manifest.cache_clear()

with patch(
"policyengine.provenance.manifest.get_data_release_manifest",
"policyengine.provenance.manifest.requests.get",
side_effect=Timeout("network timeout"),
):
try:
certify_data_release_compatibility(
"us",
runtime_model_version="1.602.0",
)
except Timeout as error:
assert "network timeout" in str(error)
except DataReleaseManifestUnavailableError as error:
assert "Could not fetch" in str(error)
else:
raise AssertionError("Expected timeout to propagate")
raise AssertionError("Expected offline mismatched version to fail")

def test__given_offline_hf__then_us_import_uses_bundled_certification(
self,
tmp_path,
):
sitecustomize = tmp_path / "sitecustomize.py"
sitecustomize.write_text(
"\n".join(
[
"import requests",
"from requests import Timeout",
"",
"def offline_get(*args, **kwargs):",
" raise Timeout('offline')",
"",
"requests.get = offline_get",
]
)
)
env = os.environ.copy()
existing_pythonpath = env.get("PYTHONPATH")
env["PYTHONPATH"] = (
f"{tmp_path}{os.pathsep}{existing_pythonpath}"
if existing_pythonpath
else str(tmp_path)
)

result = subprocess.run(
[
sys.executable,
"-c",
(
"import policyengine.tax_benefit_models.us as us; "
"print(us.model.data_certification.certified_by)"
),
],
capture_output=True,
text=True,
check=False,
env=env,
)

assert result.returncode == 0, result.stderr
assert "policyengine.py bundled manifest" in result.stdout

def test__given_mismatched_version_and_fingerprint__then_certification_fails(self):
get_data_release_manifest.cache_clear()
Expand Down