diff --git a/src/czml3/__init__.py b/src/czml3/__init__.py index 1b50d81..6ea2121 100644 --- a/src/czml3/__init__.py +++ b/src/czml3/__init__.py @@ -1,5 +1,5 @@ from .core import CZML_VERSION, Document, Packet -__version__ = "2.2.3" +__version__ = "2.3.0" __all__ = ["Document", "Packet", "CZML_VERSION"] diff --git a/src/czml3/base.py b/src/czml3/base.py index b18216b..ed8fcce 100644 --- a/src/czml3/base.py +++ b/src/czml3/base.py @@ -1,26 +1,25 @@ -from typing import Any +import sys -from pydantic import BaseModel, model_validator +from pydantic import BaseModel, ConfigDict, model_validator + +if sys.version_info[1] >= 11: + from typing import Self +else: + from typing_extensions import Self # pragma: no cover NON_DELETE_PROPERTIES = ["id", "delete"] class BaseCZMLObject(BaseModel): - @model_validator(mode="before") - @classmethod - def validate_model_before(cls, data: dict[str, Any]) -> Any: - if ( - data is not None - and isinstance(data, dict) - and "delete" in data - and data["delete"] - ): - return { - "delete": True, - "id": data.get("id"), - **{k: None for k in data if k not in NON_DELETE_PROPERTIES}, - } - return data + model_config = ConfigDict(extra="forbid") + + @model_validator(mode="after") + def check_delete(self) -> Self: + if hasattr(self, "delete") and self.delete: + for k in self.model_fields: + if k not in NON_DELETE_PROPERTIES and getattr(self, k) is not None: + setattr(self, k, None) + return self def __str__(self) -> str: return self.to_json() diff --git a/src/czml3/types.py b/src/czml3/types.py index f3a016f..e82443c 100644 --- a/src/czml3/types.py +++ b/src/czml3/types.py @@ -18,7 +18,7 @@ if sys.version_info[1] >= 11: from typing import Self else: - from typing_extensions import Self + from typing_extensions import Self # pragma: no cover TYPE_MAPPING = {bool: "boolean"} diff --git a/tests/test_properties.py b/tests/test_properties.py index bcc1152..8e2f222 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -618,21 +618,32 @@ def test_material_checkerboard(): def test_position_has_delete(): - pos = Position(delete=True, cartesian=[]) - + expected_result = """{ + "delete": true +}""" + pos = Position(delete=True, cartesian=[0, 0, 0]) assert pos.delete + assert str(pos) == expected_result def test_position_list_has_delete(): - pos = PositionList(delete=True, cartesian=[]) - + expected_result = """{ + "delete": true +}""" + pos = PositionList(delete=True, cartesian=[0, 0, 0]) assert pos.delete + assert str(pos) == expected_result def test_position_list_of_lists_has_delete(): - pos = PositionListOfLists(delete=True, cartesian=[]) - + expected_result = """{ + "delete": true +}""" + pos = PositionListOfLists( + delete=True, cartesian=[[20.0, 20.0, 0.0], [10.0, 10.0, 0.0]] + ) assert pos.delete + assert str(pos) == expected_result def test_position_no_values_raises_error(): @@ -787,9 +798,12 @@ def test_viewfrom_cartesian(): def test_viewfrom_has_delete(): - v = ViewFrom(delete=True, cartesian=[14.0, 12.0]) - + expected_result = """{ + "delete": true +}""" + v = ViewFrom(delete=True, cartesian=[14.0, 12.0, 1.0]) assert v.delete + assert str(v) == expected_result def test_viewfrom_no_values_raises_error(): @@ -1789,6 +1803,26 @@ def test_packet_billboard(): assert str(packet) == expected_result +def test_delete(): + expected_result = """{ + "delete": true +}""" + p = PositionList( + cartographicDegrees=CartographicDegreesListValue(values=[20, 30, 10]), + delete=True, + ) + assert str(p) == expected_result + + +def test_forbid_extras(): + with pytest.raises(ValidationError): + PositionList( + cartographicDegrees=CartographicDegreesListValue(values=[20, 30, 10]), + delete=True, + a=1, # type: ignore[call-arg] + ) + + # @pytest.mark.xfail(reason="Reference value needs further clarifying") # def test_uri_ref(): # expected_result = """{