Skip to content
Open
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
16 changes: 16 additions & 0 deletions dandi/dandiapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@
lgr = get_logger()


def set_asset_schema_key(metadata: dict[str, Any]) -> None:
"""
Set ``schemaKey`` to ``"Asset"`` on metadata bound for the server as asset metadata.

However the metadata was gathered (e.g. as a ``BareAsset``), if it is to be uploaded
to the server to populate, in part, an ``Asset`` instance, set the ``schemaKey`` of
the metadata to ``"Asset"`` here. The server expects the uploaded metadata to carry
``"Asset"`` as its ``schemaKey``.

The metadata is modified in place.
"""
metadata["schemaKey"] = "Asset"


class AssetType(Enum):
"""
.. versionadded:: 0.36.0
Expand Down Expand Up @@ -1993,6 +2007,7 @@ def set_raw_metadata(self, metadata: dict[str, Any]) -> None:
Set the metadata for the asset on the server to the given value and
update the `RemoteBlobAsset` in place.
"""
set_asset_schema_key(metadata)
data = self.client.put(
self.api_path, json={"metadata": metadata, "blob_id": self.blob}
)
Expand All @@ -2016,6 +2031,7 @@ def set_raw_metadata(self, metadata: dict[str, Any]) -> None:
Set the metadata for the asset on the server to the given value and
update the `RemoteZarrAsset` in place.
"""
set_asset_schema_key(metadata)
data = self.client.put(
self.api_path, json={"metadata": metadata, "zarr_id": self.zarr}
)
Expand Down
8 changes: 7 additions & 1 deletion dandi/files/bases.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@
import requests

import dandi
from dandi.dandiapi import RemoteAsset, RemoteDandiset, RESTFullAPIClient
from dandi.dandiapi import (
RemoteAsset,
RemoteDandiset,
RESTFullAPIClient,
set_asset_schema_key,
)
from dandi.metadata.core import get_default_metadata
from dandi.misctypes import DUMMY_DANDI_ETAG, Digest, LocalReadableFile, P
from dandi.utils import post_upload_size_check, pre_upload_size_check, yaml_load
Expand Down Expand Up @@ -363,6 +368,7 @@ def iter_upload(
from dandi.support.digests import get_dandietag

asset_path = metadata.setdefault("path", self.path)
set_asset_schema_key(metadata)
client = dandiset.client
yield {"status": "calculating etag"}
etagger = get_dandietag(self.filepath)
Expand Down
2 changes: 2 additions & 0 deletions dandi/files/zarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
RemoteZarrAsset,
RemoteZarrEntry,
RESTFullAPIClient,
set_asset_schema_key,
)
from dandi.exceptions import UploadError
from dandi.metadata.core import get_default_metadata
Expand Down Expand Up @@ -580,6 +581,7 @@ def iter_upload(
`RemoteAsset`.
"""
asset_path = metadata.setdefault("path", self.path)
set_asset_schema_key(metadata)
client = dandiset.client
lgr.debug("%s: Producing asset", asset_path)
yield {"status": "producing asset"}
Expand Down
2 changes: 1 addition & 1 deletion dandi/tests/data/metadata/metadata2asset.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "dandiasset:0b0a1a0b-e3ea-4cf6-be94-e02c830d54be",
"schemaKey": "Asset",
"schemaKey": "ANYTHING GOOD NOW -- `schemaKey` of a `BareAsset` instance will be adjusted to the default value of the `schemaKey` field in the Pydantic `BareAsset` model",
"schemaVersion": "0.4.1",
"keywords": [
"test",
Expand Down
2 changes: 1 addition & 1 deletion dandi/tests/data/metadata/metadata2asset_3.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "dandiasset:0b0a1a0b-e3ea-4cf6-be94-e02c830d54be",
"schemaKey": "Asset",
"schemaKey": "ANYTHING GOOD NOW -- `schemaKey` of a `BareAsset` instance will be adjusted to the default value of the `schemaKey` field in the Pydantic `BareAsset` model",
"schemaVersion": "0.4.1",
"keywords": [
"test",
Expand Down
2 changes: 1 addition & 1 deletion dandi/tests/data/metadata/metadata2asset_cellline.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "dandiasset:0b0a1a0b-e3ea-4cf6-be94-e02c830d54be",
"schemaKey": "Asset",
"schemaKey": "ANYTHING GOOD NOW -- `schemaKey` of a `BareAsset` instance will be adjusted to the default value of the `schemaKey` field in the Pydantic `BareAsset` model",
"schemaVersion": "0.4.1",
"keywords": [
"test",
Expand Down
2 changes: 1 addition & 1 deletion dandi/tests/data/metadata/metadata2asset_simple1.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "dandiasset:bfc23fb6192b41c083a7257e09a3702b",
"schemaKey": "Asset",
"schemaKey": "ANYTHING GOOD NOW -- `schemaKey` of a `BareAsset` instance will be adjusted to the default value of the `schemaKey` field in the Pydantic `BareAsset` model",
"schemaVersion": "0.4.1",
"keywords": [
"keyword1",
Expand Down
19 changes: 14 additions & 5 deletions dandi/tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,11 +418,22 @@ def test_prepare_metadata(filename: str, metadata: dict[str, Any]) -> None:
with (METADATA_DIR / filename).open() as fp:
data_as_dict = json.load(fp)
data_as_dict["schemaVersion"] = DANDI_SCHEMA_VERSION
# `prepare_metadata()` returns a `BareAsset`, so the top-level `schemaKey`
# of `data` is the default of the `schemaKey` field of `BareAsset` of the
# installed dandischema: "Asset" historically, but "BareAsset" once
# https://github.com/dandi/dandi-schema/pull/419 lands, which pins
# `BareAsset.schemaKey` to its own class name. The JSON files store a
# placeholder for this field, so set it to the current default here to keep
# the comparison independent of the installed dandischema version.
data_as_dict["schemaKey"] = BareAsset.model_fields["schemaKey"].default
assert data == data_as_dict

# `data_as_dict` is a BareAsset; the following lines turn it into an Asset
# instance so that `validate()` below checks it against the Asset class: set
# its `schemaKey`, and add the Asset-only fields `identifier` and (as of
# schema-0.5.0, https://github.com/dandi/dandischema/pull/52) `contentUrl`.
data_as_dict["schemaKey"] = "Asset"
data_as_dict["identifier"] = "0b0a1a0b-e3ea-4cf6-be94-e02c830d54be"
# as of schema-0.5.0 (https://github.com/dandi/dandischema/pull/52)
# contentUrl is required, and validate below would map into Asset,
# due to schemaKey
if Version(DANDI_SCHEMA_VERSION) >= Version("0.5.0"):
data_as_dict["contentUrl"] = ["http://example.com"]
validate(data_as_dict)
Expand Down Expand Up @@ -1135,7 +1146,6 @@ def test_nwb2asset(simple2_nwb: Path) -> None:
# Classes with ANY_AWARE_DATETIME fields need to be constructed with
# model_construct()
assert nwb2asset(simple2_nwb, digest=DUMMY_DANDI_ETAG) == BareAsset.model_construct(
schemaKey="Asset",
schemaVersion=DANDI_SCHEMA_VERSION,
keywords=["keyword1", "keyword 2"],
access=[
Expand Down Expand Up @@ -1218,7 +1228,6 @@ def test_nwb2asset_remote_asset(nwb_dandiset: SampleDandiset) -> None:
# Classes with ANY_AWARE_DATETIME fields need to be constructed with
# model_construct()
assert nwb2asset(r, digest=digest) == BareAsset.model_construct(
schemaKey="Asset",
schemaVersion=DANDI_SCHEMA_VERSION,
keywords=["keyword1", "keyword 2"],
access=[
Expand Down
4 changes: 4 additions & 0 deletions dandi/tests/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ def test_upload_download(
(dspath / parent).mkdir()
copyfile(nwb_file, dspath / parent / name)
new_dandiset.upload()
# The uploaded asset's metadata is derived from a ``BareAsset``, but it must
# be uploaded and stored with a ``schemaKey`` of `"Asset"`
(asset,) = d.get_assets()
assert asset.get_raw_metadata()["schemaKey"] == "Asset"
download(d.version_api_url, tmp_path)
assert list_paths(tmp_path) == [
tmp_path / d.identifier / dandiset_metadata_file,
Expand Down
Loading