Skip to content
Merged
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
2 changes: 2 additions & 0 deletions clams/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ def sign_view(self, view: View, runtime_conf: dict) -> None:
:param runtime_conf: runtime configuration of the app as k-v pairs
"""
view.metadata.app = str(self.metadata.identifier)
if self.metadata.app_tags:
view.metadata.set_additional_property('appTags', list(self.metadata.app_tags))
params_map = {p.name: p for p in self.metadata.parameters}
if self._RAW_PARAMS_KEY in runtime_conf:
for k, v in runtime_conf.items():
Expand Down
21 changes: 21 additions & 0 deletions clams/appmetadata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ class AppMetadata(pydantic.BaseModel):
None,
description="(optional) A string-to-string map that can be used to store any additional metadata of the app."
)
app_tags: List[str] = pydantic.Field(
[],
description="(optional) A list of short string labels that classify what kind of work this app does "
"(e.g. task name, output profile family). Used by downstream consumers as a first-pass filter "
"for selecting views; not a substitute for inspecting actual output types and properties. "
"The values declared here are propagated by the SDK into the ``appTags`` field of every view "
"the app signs."
)
est_gpu_mem_min: int = pydantic.Field(
0,
description="(optional) Minimum GPU memory required to run the app, in megabytes (MB). "
Expand Down Expand Up @@ -472,6 +480,19 @@ def add_input_oneof(self, *inputs: Union[str, Input, vocabulary.ThingTypesBase])
newinputs.append(i)
self.input.append(newinputs)

def add_app_tag(self, *tags: str) -> None:
"""
Helper method to add one or more strings to the ``app_tags`` list,
skipping any value that is already present.

:param tags: one or more tag strings to add
"""
for tag in tags:
if not isinstance(tag, str) or not tag:
raise ValueError(f"app tag must be a non-empty string: {tag!r}")
if tag not in self.app_tags:
self.app_tags.append(tag)

def add_output(self, at_type: Union[str, vocabulary.ThingTypesBase], **properties) -> Output:
"""
Helper method to add an element to the ``output`` list.
Expand Down
27 changes: 27 additions & 0 deletions tests/test_clamsapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,33 @@ def test_sign_view(self):
self.assertEqual(len(v4.metadata.appConfiguration), 6)
self.assertEqual(len(v4.metadata.parameters['multivalued_param']), len(str(multiple_values)))

def test_app_tags_default_empty(self):
# apps that don't declare tags should not write an appTags field to view metadata
self.assertEqual(self.app.metadata.app_tags, [])
m = Mmif(self.in_mmif)
v = m.new_view()
self.app.sign_view(v, {})
self.assertIsNone(v.metadata.get('appTags'))

def test_app_tags_propagated_to_view(self):
# tags declared on app metadata should appear verbatim in signed views
self.app.metadata.add_app_tag('TemporalSegmentation', 'BarsDetection')
m = Mmif(self.in_mmif)
v = m.new_view()
self.app.sign_view(v, {})
self.assertEqual(
v.metadata.get('appTags'),
['TemporalSegmentation', 'BarsDetection'],
)

def test_add_app_tag_dedupes_and_validates(self):
self.app.metadata.add_app_tag('foo', 'bar', 'foo')
self.assertEqual(self.app.metadata.app_tags, ['foo', 'bar'])
with self.assertRaises(ValueError):
self.app.metadata.add_app_tag('')
with self.assertRaises(ValueError):
self.app.metadata.add_app_tag(123) # type: ignore[arg-type]

def test_annotate(self):
# The example app is hard-coded to **always** emit version mismatch warning
out_mmif = self.app.annotate(self.in_mmif)
Expand Down
Loading