From d34c40cbf65fddac30460260be2cc3da23e4e28e Mon Sep 17 00:00:00 2001 From: Isaac To Date: Mon, 12 Jan 2026 13:19:33 -0800 Subject: [PATCH 1/3] feat: add `otherIdentifiers` to `Dandiset` model --- dandischema/models.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/dandischema/models.py b/dandischema/models.py index b35275fe..bb2f8dd5 100644 --- a/dandischema/models.py +++ b/dandischema/models.py @@ -1677,6 +1677,25 @@ def contributor_musthave_contact( pattern=rf"^{ID_PATTERN}:\d{{6}}$", json_schema_extra={"readOnly": True, "nskey": "schema"}, ) + + otherIdentifiers: Annotated[ + list[ + Annotated[ + str, StringConstraints(pattern=rf"^{UNVENDORED_ID_PATTERN}:\d{{6}}$") + ] + ], + Field( + default_factory=list, + title="Other Dandiset identifiers", + description="Alternative Dandiset identifiers for this Dandiset as it is " + "identified in other DANDI instances.", + json_schema_extra={ + "readOnly": True, + "nskey": DANDI_NSKEY, + }, + ), + ] + name: str = Field( title="Dandiset title", description="A title associated with the Dandiset.", From 813b8d9153603d7f7b315339176f51d9f83b16c2 Mon Sep 17 00:00:00 2001 From: Isaac To Date: Tue, 13 Jan 2026 11:00:20 -0800 Subject: [PATCH 2/3] test: provide basic Dandiset metadata through fixture So that the metadata can be used amount different tests --- dandischema/tests/test_models.py | 102 ++++++++++++++++++------------- 1 file changed, 60 insertions(+), 42 deletions(-) diff --git a/dandischema/tests/test_models.py b/dandischema/tests/test_models.py index 8bb87ccb..ac2d1d4a 100644 --- a/dandischema/tests/test_models.py +++ b/dandischema/tests/test_models.py @@ -41,6 +41,53 @@ _INSTANCE_CONFIG = get_instance_config() +@pytest.fixture +def base_dandiset_metadata() -> dict[str, Any]: + """ + Fixture providing basic Dandiset metadata for constructing a `Dandiset` instance. + + Returns: + Dict[str, Any] + A dictionary containing basic Dandiset metadata without `doi`, `datePublished`, + and `publishedBy`, suitable for constructing a `Dandiset` instance but not a + `PublishedDandiset` instance. + + Note: + This metadata is returned by a fixture to ensure that each test receives a fresh + copy of the metadata dictionary. + """ + + return { + "identifier": f"{INSTANCE_NAME}:999999", + "id": f"{INSTANCE_NAME}:999999/draft", + "version": "1.0.0", + "name": "testing dataset", + "description": "testing", + "contributor": [ + { + "name": "last name, first name", + "email": "someone@dandiarchive.org", + "roleName": [RoleType("dcite:ContactPerson")], + "schemaKey": "Person", + } + ], + "license": [LicenseType("spdx:CC-BY-4.0")], + "citation": "Last, first (2021). Test citation.", + "assetsSummary": { + "numberOfBytes": 0, + "numberOfFiles": 0, + "dataStandard": [{"name": "NWB"}], + "approach": [{"name": "electrophysiology"}], + "measurementTechnique": [{"name": "two-photon microscopy technique"}], + "species": [{"name": "Human"}], + }, + "manifestLocation": [ + "https://api.dandiarchive.org/api/dandisets/999999/versions/draft/assets/" + ], + "url": "https://dandiarchive.org/dandiset/999999/draft", + } + + @pytest.mark.parametrize( ("y_type", "anys_value"), [ @@ -403,46 +450,15 @@ def test_autogenerated_titles() -> None: @skipif_no_doi_prefix -def test_dandimeta_1() -> None: +def test_dandimeta_1(base_dandiset_metadata: dict[str, Any]) -> None: """checking basic metadata for publishing""" assert DOI_PREFIX is not None - # metadata without doi, datePublished and publishedBy - meta_dict: Dict[str, Any] = { - "identifier": f"{INSTANCE_NAME}:999999", - "id": f"{INSTANCE_NAME}:999999/draft", - "version": "1.0.0", - "name": "testing dataset", - "description": "testing", - "contributor": [ - { - "name": "last name, first name", - "email": "someone@dandiarchive.org", - "roleName": [RoleType("dcite:ContactPerson")], - "schemaKey": "Person", - } - ], - "license": [LicenseType("spdx:CC-BY-4.0")], - "citation": "Last, first (2021). Test citation.", - "assetsSummary": { - "numberOfBytes": 0, - "numberOfFiles": 0, - "dataStandard": [{"name": "NWB"}], - "approach": [{"name": "electrophysiology"}], - "measurementTechnique": [{"name": "two-photon microscopy technique"}], - "species": [{"name": "Human"}], - }, - "manifestLocation": [ - "https://api.dandiarchive.org/api/dandisets/999999/versions/draft/assets/" - ], - "url": "https://dandiarchive.org/dandiset/999999/draft", - } - # should work for Dandiset but PublishedDandiset should raise an error - Dandiset(**meta_dict) + Dandiset(**base_dandiset_metadata) with pytest.raises(ValidationError) as exc: - PublishedDandiset(**meta_dict) + PublishedDandiset(**base_dandiset_metadata) ErrDetail = namedtuple("ErrDetail", ["type", "msg"]) @@ -490,21 +506,23 @@ def test_dandimeta_1() -> None: # after adding basic meta required to publish: doi, datePublished, publishedBy, assetsSummary, # so PublishedDandiset should work - meta_dict["url"] = "https://dandiarchive.org/dandiset/999999/0.0.0" - meta_dict["id"] = f"{INSTANCE_NAME}:999999/0.0.0" - meta_dict["version"] = "0.0.0" - meta_dict.update( + base_dandiset_metadata["url"] = "https://dandiarchive.org/dandiset/999999/0.0.0" + base_dandiset_metadata["id"] = f"{INSTANCE_NAME}:999999/0.0.0" + base_dandiset_metadata["version"] = "0.0.0" + base_dandiset_metadata.update( basic_publishmeta(INSTANCE_NAME, dandi_id="999999", prefix=DOI_PREFIX) ) - meta_dict["assetsSummary"].update(**{"numberOfBytes": 1, "numberOfFiles": 1}) + base_dandiset_metadata["assetsSummary"].update( + **{"numberOfBytes": 1, "numberOfFiles": 1} + ) # Test that releaseNotes is optional (can be omitted) - dandiset_without_notes = PublishedDandiset(**meta_dict) + dandiset_without_notes = PublishedDandiset(**base_dandiset_metadata) assert dandiset_without_notes.releaseNotes is None # Test that releaseNotes can be set to a string value - meta_dict["releaseNotes"] = "Releasing during testing" - dandiset_with_notes = PublishedDandiset(**meta_dict) + base_dandiset_metadata["releaseNotes"] = "Releasing during testing" + dandiset_with_notes = PublishedDandiset(**base_dandiset_metadata) assert dandiset_with_notes.releaseNotes == "Releasing during testing" # Test that releaseNotes appears in model_dump From 498cdacad6fee03825563a9c1cf2c5fa9fff2bf1 Mon Sep 17 00:00:00 2001 From: Isaac To Date: Tue, 13 Jan 2026 11:30:21 -0800 Subject: [PATCH 3/3] test: provide tests for `Dandiset.otherIdentifiers` --- dandischema/tests/test_models.py | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/dandischema/tests/test_models.py b/dandischema/tests/test_models.py index ac2d1d4a..ffb6f358 100644 --- a/dandischema/tests/test_models.py +++ b/dandischema/tests/test_models.py @@ -1001,3 +1001,63 @@ class VendoredFieldModel(BaseModel): # Validate the invalid vendored fields against the vendored patterns with pytest.raises(ValidationError): VendoredFieldModel.model_validate(invalid_vendored_fields) + + +class TestOtherIdentifiers: + def test_not_specified(self, base_dandiset_metadata: dict[str, Any]) -> None: + """ + Test the case that `otherIdentifiers` is not specified + """ + dandiset = Dandiset.model_validate(base_dandiset_metadata) + assert dandiset.otherIdentifiers == [] + + def test_empty_list(self, base_dandiset_metadata: dict[str, Any]) -> None: + """ + Test the case that `otherIdentifiers` is an empty list + """ + base_dandiset_metadata["otherIdentifiers"] = [] + dandiset = Dandiset.model_validate(base_dandiset_metadata) + assert dandiset.otherIdentifiers == [] + + @pytest.mark.parametrize( + "identifiers", + [ + ["DANDI-SANDBOX:123456"], + ["EMBER-DANDI:123456"], + ["DANDI-SANDBOX:123456", "EMBER-DANDI:123456"], + ["A:123456", "B:654321"], + ], + ) + def test_with_valid_identifiers( + self, identifiers: list[str], base_dandiset_metadata: dict[str, Any] + ) -> None: + """ + Test the case that `otherIdentifiers` is set to a list of valid identifiers + """ + base_dandiset_metadata["otherIdentifiers"] = identifiers + dandiset = Dandiset.model_validate(base_dandiset_metadata) + assert dandiset.otherIdentifiers == identifiers + + @pytest.mark.parametrize( + "identifiers", + [ + # List of invalid identifiers + ["-A:123456"], + ["DANDI-SANDBOX:12345"], + ["DANDI-SANDBOX:123456", "EMBER-DANDI123456"], + [42], + # Value that is not a list + "DANDI-SANDBOX:123456", + 42, + ], + ) + def test_with_invalid_identifiers( + self, identifiers: Any, base_dandiset_metadata: dict[str, Any] + ) -> None: + """ + Test the case that `otherIdentifiers` is set to a list of invalid identifiers + or a value that is not a list + """ + base_dandiset_metadata["otherIdentifiers"] = identifiers + with pytest.raises(ValidationError): + Dandiset.model_validate(base_dandiset_metadata)