diff --git a/pyiceberg/catalog/rest/__init__.py b/pyiceberg/catalog/rest/__init__.py index 88bbd29bef..c261dcb06f 100644 --- a/pyiceberg/catalog/rest/__init__.py +++ b/pyiceberg/catalog/rest/__init__.py @@ -447,7 +447,12 @@ def replace_none_with_dict(cls, v: Any) -> Properties: class UpdateNamespacePropertiesResponse(IcebergBaseModel): removed: list[str] = Field() updated: list[str] = Field() - missing: list[str] = Field() + # optional and nullable in the REST spec; servers may omit it when nothing is missing + missing: list[str] = Field(default_factory=list) + + @field_validator("missing", mode="before") + def missing_null_to_empty(cls, missing: list[str] | None) -> list[str]: + return missing if missing is not None else [] class ListTableResponseEntry(IcebergBaseModel): diff --git a/tests/catalog/test_rest.py b/tests/catalog/test_rest.py index b06281bcef..31801d25cb 100644 --- a/tests/catalog/test_rest.py +++ b/tests/catalog/test_rest.py @@ -1342,6 +1342,25 @@ def test_update_namespace_properties_200(rest_mock: Mocker) -> None: assert response == PropertiesUpdateSummary(removed=[], updated=["prop"], missing=["abc"]) +@pytest.mark.parametrize( + "response_json", + [ + {"removed": ["a", "b"], "updated": []}, + {"removed": ["a", "b"], "updated": [], "missing": None}, + ], +) +def test_update_namespace_properties_200_without_missing(rest_mock: Mocker, response_json: dict[str, Any]) -> None: + rest_mock.post( + f"{TEST_URI}v1/namespaces/fokko/properties", + json=response_json, + status_code=200, + request_headers=TEST_HEADERS, + ) + response = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).update_namespace_properties(("fokko",), {"a", "b"}) + + assert response == PropertiesUpdateSummary(removed=["a", "b"], updated=[], missing=[]) + + def test_namespace_exists_200(rest_mock: Mocker) -> None: rest_mock.head( f"{TEST_URI}v1/namespaces/fokko",