From 0ca8ca0d9e430af311ab0d472d237ce05a7919be Mon Sep 17 00:00:00 2001 From: tmchartrand Date: Tue, 19 May 2026 10:50:17 -0700 Subject: [PATCH 1/3] add name field to DataAsset (#1787) infers name from url if in s3://aind-open-data/ --- .../components/identifiers.py | 17 +++++++++- tests/test_identifiers.py | 34 ++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/aind_data_schema/components/identifiers.py b/src/aind_data_schema/components/identifiers.py index a9310b882..a3d4f7731 100644 --- a/src/aind_data_schema/components/identifiers.py +++ b/src/aind_data_schema/components/identifiers.py @@ -1,5 +1,6 @@ """Schema for identifiers""" +import re from enum import Enum from pathlib import Path from typing import Dict, List, Optional @@ -26,7 +27,21 @@ class Database(str, Enum): class DataAsset(DataModel): """Description of a single data asset""" - url: str = Field(..., title="Asset location", description="URL pointing to the data asset") + name: Optional[str] = Field(default=None, title="Asset name", description="Name of the data asset") + url: Optional[str] = Field(default=None, title="Asset location", description="URL pointing to the data asset") + + @model_validator(mode="after") + def validate_name(self): + """Validator to be sure name or url is provided + If name isn't provided, attempt to parse name from url. If url is also not provided, raise error. + """ + if not self.name: + if not self.url: + raise ValueError("Either 'name' or 'url' must be provided for a DataAsset.") + match = re.match("^s3://aind-open-data/([^/]+)(/.*)?$", self.url) + if match is not None: + self.name = match.group(1) + return self class CombinedData(DataModel): diff --git a/tests/test_identifiers.py b/tests/test_identifiers.py index 155341ab3..f37176302 100644 --- a/tests/test_identifiers.py +++ b/tests/test_identifiers.py @@ -4,7 +4,7 @@ from pydantic import ValidationError -from aind_data_schema.components.identifiers import Code, Person +from aind_data_schema.components.identifiers import Code, DataAsset, Person class Testexperimenter(unittest.TestCase): @@ -52,6 +52,38 @@ def test_git_hash_invalid(self): with self.subTest(git_hash=git_hash): with self.assertRaises(ValidationError): Code(url="https://github.com/org/repo", commit_hash=git_hash) +class TestDataAsset(unittest.TestCase): + """Test DataAsset validator""" + + def test_name_provided_directly(self): + """Name is kept as-is when explicitly provided""" + asset = DataAsset(name="my-dataset") + self.assertEqual(asset.name, "my-dataset") + + def test_name_parsed_from_url_no_subpath(self): + """Name is inferred from top-level prefix with no nested path""" + asset = DataAsset(url="s3://aind-open-data/my-dataset") + self.assertEqual(asset.name, "my-dataset") + + def test_name_parsed_from_url_with_subpath(self): + """Name is inferred from top-level prefix, ignoring nested path""" + asset = DataAsset(url="s3://aind-open-data/my-dataset/sub/path/file.txt") + self.assertEqual(asset.name, "my-dataset") + + def test_name_not_overridden_when_provided_with_url(self): + """Explicit name takes precedence over URL-inferred name""" + asset = DataAsset(name="explicit-name", url="s3://aind-open-data/other-dataset/sub") + self.assertEqual(asset.name, "explicit-name") + + def test_neither_name_nor_url_raises(self): + """Raises ValidationError when neither name nor url is provided""" + with self.assertRaises(ValidationError): + DataAsset() + + def test_url_wrong_bucket_leaves_name_none(self): + """URL from a different bucket does not set name (remains None)""" + asset = DataAsset(url="s3://other-bucket/my-dataset") + self.assertIsNone(asset.name) if __name__ == "__main__": From a00390b2e69d5d3fe17c8d66ea61c5174239abbc Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 May 2026 18:17:47 +0000 Subject: [PATCH 2/3] update docs --- docs/source/aind_data_schema_models/process_names.md | 1 + docs/source/components/identifiers.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/source/aind_data_schema_models/process_names.md b/docs/source/aind_data_schema_models/process_names.md index 557b8d904..1bafd1836 100644 --- a/docs/source/aind_data_schema_models/process_names.md +++ b/docs/source/aind_data_schema_models/process_names.md @@ -37,6 +37,7 @@ Process names | `IMAGE_TILE_ALIGNMENT` | `Image tile alignment` | | `IMAGE_TILE_FUSING` | `Image tile fusing` | | `IMAGE_TILE_PROJECTION` | `Image tile projection` | +| `MANUAL_CURATION` | `Manual curation` | | `MODEL_EVALUATION` | `Model evaluation` | | `MODEL_TRAINING` | `Model training` | | `NEURON_SKELETON_PROCESSING` | `Neuron skeleton processing` | diff --git a/docs/source/components/identifiers.md b/docs/source/components/identifiers.md index b26cc6f62..cef44a136 100644 --- a/docs/source/components/identifiers.md +++ b/docs/source/components/identifiers.md @@ -50,7 +50,8 @@ Description of a single data asset | Field | Type | Title (Description) | |-------|------|-------------| -| `url` | `str` | Asset location (URL pointing to the data asset) | +| `name` | `Optional[str]` | Asset name (Name of the data asset) | +| `url` | `Optional[str]` | Asset location (URL pointing to the data asset) | ### Database From 535d84b03255c3d9701bd590ba0e4f5f26185767 Mon Sep 17 00:00:00 2001 From: tmchartrand Date: Tue, 19 May 2026 13:11:36 -0700 Subject: [PATCH 3/3] chore: lint --- tests/test_identifiers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_identifiers.py b/tests/test_identifiers.py index f37176302..1a9a84aed 100644 --- a/tests/test_identifiers.py +++ b/tests/test_identifiers.py @@ -52,6 +52,8 @@ def test_git_hash_invalid(self): with self.subTest(git_hash=git_hash): with self.assertRaises(ValidationError): Code(url="https://github.com/org/repo", commit_hash=git_hash) + + class TestDataAsset(unittest.TestCase): """Test DataAsset validator"""