Skip to content

fix Link 'label:assets' field type as list #183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

## Unreleased

- Fix invalid `label:assets` type in `stac_pydantic.links.Link`.
A single `str` was specified instead of `List[str]` as expected by the extension field
(see [Label Extension - Links](https://github.com/stac-extensions/label?tab=readme-ov-file#links-source-imagery)) (#183, @fmigneault).
- Add validation of `Link` ensuring that `rel=source` is employed if `label:assets` is specified (#183, @fmigneault).

## 3.3.2 (2025-06-18)

- Remove restriction on valid media types for links (#182, @mishaschwartz)
Expand Down
22 changes: 19 additions & 3 deletions stac_pydantic/links.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from enum import auto
from typing import Iterator, List, Optional, Union
from typing import Iterator, List, Optional, Union, Any
from urllib.parse import urljoin

from pydantic import ConfigDict, Field, RootModel
from pydantic import AliasChoices, ConfigDict, Field, RootModel, ValidationInfo, field_validator

from stac_pydantic.shared import MimeTypes, StacBaseModel
from stac_pydantic.utils import AutoValueEnum
Expand All @@ -19,13 +19,29 @@ class Link(StacBaseModel):
title: Optional[str] = None

# Label extension
label: Optional[str] = Field(default=None, alias="label:assets")
label: Optional[List[str]] = Field(
default=None,
min_length=1,
alias="label:assets",
validation_alias=AliasChoices("label:assets", "label_assets", "label"),
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 well to be honest I believe we should just remove the label attribute and let extension be handled with validate_extensions

https://github.com/stac-utils/stac-pydantic?tab=readme-ov-file#extensions

Copy link
Contributor Author

@fmigneault fmigneault Jul 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. That makes the most sense IMO as well.
Are all extensions validated automatically by STAC-FastAPI when POSTing a STAC Item?
I don't see explicit calls to validate_extensions in the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think they are validated but we could add an option. Can you open an issue in stac-fastapi?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I can look into adding that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feature ready: stac-utils/stac-fastapi-pgstac#269

Replacement for this PR: #184

model_config = ConfigDict(use_enum_values=True, extra="allow")

def resolve(self, base_url: str) -> None:
"""resolve a link to the given base URL"""
self.href = urljoin(base_url, self.href)

@field_validator("label", mode="after")
@classmethod
def validate_label_rel_source(cls, label: Optional[List[str]], info: ValidationInfo) -> Optional[List[str]]:
# check requirement: https://github.com/stac-extensions/label#links-source-imagery
if label and info.data["rel"] != "source":
raise ValueError(
"Label extension link with 'label:assets' is only allowed for link with 'rel=source'. "
f"Values ('rel': {info.data['rel']}, 'label:assets': {label})"
)
return label


class Links(RootModel[List[Link]]):
root: List[Link]
Expand Down
4 changes: 3 additions & 1 deletion tests/example_stac/roads_item.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@
"href": "roads_source.json",
"rel": "source",
"title": "The source imagery these road labels were derived from",
"label:assets": "road_labels"
"label:assets": [
"road_labels"
]
}
],
"assets": {
Expand Down
9 changes: 9 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ def test_label_extension() -> None:
dict_match(test_item, valid_item)


def test_label_extension_rel_source() -> None:
with pytest.raises(ValidationError):
Link(href=LABEL_EXTENSION, rel="random", label_assets=["road_labels"])

link = Link(href=LABEL_EXTENSION, rel="source", label_assets=["road_labels"])
assert link.rel == "source"
assert link.label == ["road_labels"]


def test_explicit_extension_validation() -> None:
test_item = request(EO_EXTENSION)

Expand Down