Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions docs/source/aind_data_schema_models/process_names.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
Expand Down
3 changes: 2 additions & 1 deletion docs/source/components/identifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 16 additions & 1 deletion src/aind_data_schema/components/identifiers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Schema for identifiers"""

import re
from enum import Enum
from pathlib import Path
from typing import Dict, List, Optional
Expand All @@ -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):
Expand Down
34 changes: 33 additions & 1 deletion tests/test_identifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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__":
Expand Down
Loading