Skip to content

Commit ecd157d

Browse files
committed
Treat missing as optional in UpdateNamespacePropertiesResponse
The REST spec marks the missing field of UpdateNamespacePropertiesResponse as optional and nullable, and servers such as Lakekeeper omit it when no requested removal was missing. pyiceberg required it, so update_namespace_properties raised a ValidationError after the server had applied the change. Default it to an empty list and map null to []. Closes #4066
1 parent 068aae5 commit ecd157d

2 files changed

Lines changed: 25 additions & 1 deletion

File tree

‎pyiceberg/catalog/rest/__init__.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,12 @@ def replace_none_with_dict(cls, v: Any) -> Properties:
447447
class UpdateNamespacePropertiesResponse(IcebergBaseModel):
448448
removed: list[str] = Field()
449449
updated: list[str] = Field()
450-
missing: list[str] = Field()
450+
# optional and nullable in the REST spec; servers may omit it when nothing is missing
451+
missing: list[str] = Field(default_factory=list)
452+
453+
@field_validator("missing", mode="before")
454+
def missing_null_to_empty(cls, missing: list[str] | None) -> list[str]:
455+
return missing if missing is not None else []
451456

452457

453458
class ListTableResponseEntry(IcebergBaseModel):

‎tests/catalog/test_rest.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,25 @@ def test_update_namespace_properties_200(rest_mock: Mocker) -> None:
13421342
assert response == PropertiesUpdateSummary(removed=[], updated=["prop"], missing=["abc"])
13431343

13441344

1345+
@pytest.mark.parametrize(
1346+
"response_json",
1347+
[
1348+
{"removed": ["a", "b"], "updated": []},
1349+
{"removed": ["a", "b"], "updated": [], "missing": None},
1350+
],
1351+
)
1352+
def test_update_namespace_properties_200_without_missing(rest_mock: Mocker, response_json: dict[str, Any]) -> None:
1353+
rest_mock.post(
1354+
f"{TEST_URI}v1/namespaces/fokko/properties",
1355+
json=response_json,
1356+
status_code=200,
1357+
request_headers=TEST_HEADERS,
1358+
)
1359+
response = RestCatalog("rest", uri=TEST_URI, token=TEST_TOKEN).update_namespace_properties(("fokko",), {"a", "b"})
1360+
1361+
assert response == PropertiesUpdateSummary(removed=["a", "b"], updated=[], missing=[])
1362+
1363+
13451364
def test_namespace_exists_200(rest_mock: Mocker) -> None:
13461365
rest_mock.head(
13471366
f"{TEST_URI}v1/namespaces/fokko",

0 commit comments

Comments
 (0)