diff --git a/pystac/extensions/classification.py b/pystac/extensions/classification.py index bd8b4557..5842ab41 100644 --- a/pystac/extensions/classification.py +++ b/pystac/extensions/classification.py @@ -525,8 +525,10 @@ def apply( or bitfields is None and classes is not None ), "Must set exactly one of `classes` or `bitfields`" - self.classes = classes - self.bitfields = bitfields + if classes: + self.classes = classes + if bitfields: + self.bitfields = bitfields @property def classes(self) -> list[Classification] | None: diff --git a/tests/extensions/cassettes/test_classification/test_apply_classes.yaml b/tests/extensions/cassettes/test_classification/test_apply_classes.yaml new file mode 100644 index 00000000..a4ef66e6 --- /dev/null +++ b/tests/extensions/cassettes/test_classification/test_apply_classes.yaml @@ -0,0 +1,153 @@ +interactions: +- request: + body: null + headers: + Connection: + - close + Host: + - stac-extensions.github.io + User-Agent: + - Python-urllib/3.12 + method: GET + uri: https://stac-extensions.github.io/classification/v2.0.0/schema.json + response: + body: + string: "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$id\": + \"https://stac-extensions.github.io/classification/v2.0.0/schema.json#\",\n + \ \"title\": \"Classification Extension\",\n \"description\": \"STAC Classification + Extension for STAC Items and STAC Collections.\",\n \"type\": \"object\",\n + \ \"required\": [\"stac_extensions\"],\n \"properties\": {\n \"stac_extensions\": + {\n \"type\": \"array\",\n \"contains\": {\n \"const\": \"https://stac-extensions.github.io/classification/v2.0.0/schema.json\"\n + \ }\n }\n },\n \"oneOf\": [\n {\n \"$comment\": \"This is + the schema for STAC Items.\",\n \"type\": \"object\",\n \"required\": + [\"type\", \"properties\", \"assets\"],\n \"properties\": {\n \"type\": + {\n \"const\": \"Feature\"\n },\n \"properties\": {\n + \ \"$comment\": \"This validates the fields in Item Properties, but + does not require them.\",\n \"allOf\": [\n {\n \"$ref\": + \"#/definitions/fields\"\n },\n {\n \"$ref\": + \"#/definitions/ml_model_output\"\n }\n ]\n },\n + \ \"assets\": {\n \"$comment\": \"This validates the fields + in Item Assets (including in Raster Band Objects), but does not require them.\",\n + \ \"type\": \"object\",\n \"additionalProperties\": {\n \"allOf\": + [\n {\n \"$ref\": \"#/definitions/fields\"\n },\n + \ {\n \"$ref\": \"#/definitions/raster_bands\"\n + \ },\n {\n \"$ref\": \"#/definitions/ml_model_output\"\n + \ }\n ]\n }\n }\n }\n },\n + \ {\n \"$comment\": \"This is the schema for STAC Collections.\",\n + \ \"type\": \"object\",\n \"required\": [\"type\"],\n \"properties\": + {\n \"type\": {\n \"const\": \"Collection\"\n },\n + \ \"assets\": {\n \"$comment\": \"This validates the fields + in Collection Assets, but does not require them.\",\n \"type\": \"object\",\n + \ \"additionalProperties\": {\n \"allOf\": [\n {\n + \ \"$ref\": \"#/definitions/fields\"\n },\n {\n + \ \"$ref\": \"#/definitions/raster_bands\"\n },\n + \ {\n \"$ref\": \"#/definitions/ml_model_output\"\n + \ }\n ]\n }\n },\n \"item_assets\": + {\n \"$comment\": \"This validates the fields in Item Asset Definitions, + but does not require them.\",\n \"type\": \"object\",\n \"additionalProperties\": + {\n \"allOf\": [\n {\n \"$ref\": \"#/definitions/fields\"\n + \ },\n {\n \"$ref\": \"#/definitions/raster_bands\"\n + \ },\n {\n \"$ref\": \"#/definitions/ml_model_output\"\n + \ }\n ]\n }\n },\n \"summaries\": + {\n \"$comment\": \"This validates the fields in Summaries, but does + not require them.\",\n \"$ref\": \"#/definitions/fields\"\n }\n + \ }\n }\n ],\n \"definitions\": {\n \"require_any_field\": {\n + \ \"$comment\": \"Please list all fields here so that we can force the + existance of one of them in other parts of the schemas.\",\n \"anyOf\": + [\n {\n \"required\": [\"classification:bitfields\"]\n },\n + \ {\n \"required\": [\"classification:classes\"]\n }\n + \ ]\n },\n \"fields\": {\n \"$comment\": \"Add your new fields + here. Don't require them here, do that above in the corresponding schema.\",\n + \ \"type\": \"object\",\n \"properties\": {\n \"classification:bitfields\": + {\n \"type\": \"array\",\n \"uniqueItems\": true,\n \"minItems\": + 1,\n \"items\": {\n \"$ref\": \"#/definitions/bit_field_object\"\n + \ }\n },\n \"classification:classes\": {\n \"type\": + \"array\",\n \"uniqueItems\": true,\n \"minItems\": 1,\n + \ \"items\": {\n \"$ref\": \"#/definitions/class_object\"\n + \ }\n }\n },\n \"patternProperties\": {\n \"^(?!classification:)\": + {}\n },\n \"additionalProperties\": false\n },\n \"class_object\": + {\n \"$comment\": \"Object for storing classes\",\n \"type\": \"object\",\n + \ \"required\": [\"value\", \"name\"],\n \"properties\": {\n \"value\": + {\n \"type\": \"integer\"\n },\n \"description\": {\n + \ \"type\": \"string\"\n },\n \"name\": {\n \"type\": + \"string\",\n \"pattern\": \"^[0-9A-Za-z-_]+$\"\n },\n \"title\": + {\n \"type\": \"string\"\n },\n \"color_hint\": {\n + \ \"type\": \"string\",\n \"pattern\": \"^([0-9A-Fa-f]{6})$\"\n + \ },\n \"nodata\": {\n \"type\": \"boolean\"\n },\n + \ \"percentage\": {\n \"type\": \"number\",\n \"minimum\": + 0,\n \"maximum\": 100\n },\n \"count\": {\n \"type\": + \"integer\",\n \"minimum\": 0\n }\n }\n },\n \"bit_field_object\": + {\n \"$comment\": \"Object for storing bit fields\",\n \"type\": + \"object\",\n \"required\": [\"offset\", \"length\", \"classes\"],\n + \ \"properties\": {\n \"offset\": {\n \"type\": \"integer\",\n + \ \"minimum\": 0\n },\n \"length\": {\n \"type\": + \"integer\",\n \"minimum\": 1\n },\n \"classes\": {\n + \ \"type\": \"array\",\n \"uniqueItems\": true,\n \"minItems\": + 1,\n \"items\": {\n \"$ref\": \"#/definitions/class_object\"\n + \ }\n },\n \"roles\": {\n \"type\": \"array\",\n + \ \"uniqueItems\": true,\n \"minItems\": 1,\n \"items\": + {\n \"type\": \"string\"\n }\n },\n \"description\": + {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": + \"string\",\n \"pattern\": \"^[0-9A-Za-z-_]+$\"\n }\n }\n + \ },\n \"raster_bands\": {\n \"$comment\": \"Classification fields + on the Raster Extension raster:bands object\",\n \"type\": \"object\",\n + \ \"properties\": {\n \"raster:bands\": {\n \"type\": + \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/fields\"\n + \ }\n }\n }\n },\n \"ml_model_output\": {\n \"$comment\": + \"Classification fields on the MLM Extension mlm:output objects (https://crim-ca.github.io/mlm-extension/v1.0.0/schema.json).\",\n + \ \"description\": \"Describes the classes embedded in the output of the + ML model following inference.\",\n \"type\": \"object\",\n \"properties\": + {\n \"mlm:output\": {\n \"type\": \"array\",\n \"items\": + {\n \"$ref\": \"#/definitions/fields\"\n }\n }\n + \ }\n }\n }\n}\n" + headers: + Accept-Ranges: + - bytes + Access-Control-Allow-Origin: + - '*' + Age: + - '0' + Cache-Control: + - max-age=600 + Connection: + - close + Content-Length: + - '6554' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 17 Dec 2024 23:25:05 GMT + ETag: + - '"66487a48-199a"' + Last-Modified: + - Sat, 18 May 2024 09:52:08 GMT + Server: + - GitHub.com + Strict-Transport-Security: + - max-age=31556952 + Vary: + - Accept-Encoding + Via: + - 1.1 varnish + X-Cache: + - MISS + X-Cache-Hits: + - '0' + X-Fastly-Request-ID: + - eaacd335ebd5f2b35eecf0fbe0ad4f9d82ef2870 + X-GitHub-Request-Id: + - CB53:15BE6F:C16B48:D70A6A:67620851 + X-Served-By: + - cache-den-kden1300056-DEN + X-Timer: + - S1734477906.551532,VS0,VE74 + expires: + - Tue, 17 Dec 2024 23:35:05 GMT + permissions-policy: + - interest-cohort=() + x-proxy-cache: + - MISS + status: + code: 200 + message: OK +version: 1 diff --git a/tests/extensions/test_classification.py b/tests/extensions/test_classification.py index 45070db8..8ff526b9 100644 --- a/tests/extensions/test_classification.py +++ b/tests/extensions/test_classification.py @@ -162,6 +162,25 @@ def test_apply_bitfields(plain_item: Item) -> None: ) +@pytest.mark.vcr() +def test_apply_classes(plain_item: Item) -> None: + ClassificationExtension.add_to(plain_item) + ClassificationExtension.ext(plain_item).apply( + classes=[ + Classification.create(name="no", value=0), + Classification.create(name="yes", value=1), + ] + ) + plain_item.validate() + assert ( + ClassificationExtension.ext(plain_item).classes is not None + and len( + cast(list[Classification], ClassificationExtension.ext(plain_item).classes) + ) + == 2 + ) + + def test_create_classes(plain_item: Item) -> None: ClassificationExtension.add_to(plain_item) ext = ClassificationExtension.ext(plain_item)