Skip to content
62 changes: 60 additions & 2 deletions dandischema/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Type,
TypeVar,
Union,
cast,
)
from warnings import warn

Expand Down Expand Up @@ -76,6 +77,51 @@ def diff_models(model1: M, model2: M) -> None:
print(f"{field} is different")


def get_dict_without_context(d: Any) -> Any:
"""
If a given object is a dictionary, return a copy of it without the
`@context` key. Otherwise, return the input object as is.

:param d: The given object
:return: If the object is a dictionary, a copy of it without the `@context` key;
otherwise, the input object as is.
"""
if isinstance(d, dict):
return {k: v for k, v in d.items() if k != "@context"}
return d


def add_context(json_schema: dict) -> None:
"""
Add the `@context` key to the given JSON schema

:param json_schema: The dictionary representing the JSON schema

raises: ValueError if the `@context` key is already present in the given schema
"""
context_key = "@context"
context_key_title = "@Context"
properties = cast(dict, json_schema.get("properties", {}))
required = cast(list, json_schema.get("required", []))

if context_key in properties or context_key in required:
msg = f"The '{context_key}' key is already present in the given JSON schema."
raise ValueError(msg)

properties[context_key] = {
"format": "uri",
"minLength": 1,
"title": context_key_title,
"type": "string",
}
# required.append(context_key) # Uncomment this line to make `@context` required

# Update the schema
# This is needed to handle the case in which the keys are newly created
json_schema["properties"] = properties
json_schema["required"] = required


class AccessType(Enum):
"""An enumeration of access status options"""

Expand Down Expand Up @@ -608,6 +654,8 @@ def __get_pydantic_json_schema__(

return schema

model_config = ConfigDict(extra="forbid")


class PropertyValue(DandiBaseModel):
maxValue: Optional[float] = Field(None, json_schema_extra={"nskey": "schema"})
Expand Down Expand Up @@ -1588,8 +1636,6 @@ class CommonModel(DandiBaseModel):
class Dandiset(CommonModel):
"""A body of structured information describing a DANDI dataset."""

model_config = ConfigDict(extra="allow")

@field_validator("contributor")
@classmethod
def contributor_musthave_contact(
Expand Down Expand Up @@ -1684,6 +1730,12 @@ def contributor_musthave_contact(
"nskey": "dandi",
}

# Model validator to remove the `"@context"` key from data instance before
# "base" validation is performed.
_remove_context_key = model_validator(mode="before")(get_dict_without_context)

model_config = ConfigDict(json_schema_extra=add_context)


class BareAsset(CommonModel):
"""Metadata used to describe an asset anywhere (local or server).
Expand Down Expand Up @@ -1816,6 +1868,12 @@ class Asset(BareAsset):
json_schema_extra={"readOnly": True, "nskey": "schema"}
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look here for an example on how we add extra to json schema.

May be you could add extra for your @context which would state something like "includeInUI": False


# Model validator to remove the `"@context"` key from data instance before
# "base" validation is performed.
_remove_context_key = model_validator(mode="before")(get_dict_without_context)

model_config = ConfigDict(json_schema_extra=add_context)


class Publishable(DandiBaseModel):
publishedBy: Union[AnyHttpUrl, PublishActivity] = Field(
Expand Down