Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request dandi#808 from dandi/make
Browse files Browse the repository at this point in the history
Publicly expose classmethods for constructing API resource instances
  • Loading branch information
yarikoptic authored Oct 22, 2021
2 parents 0acafbe + b0f7d33 commit 04aab25
Showing 1 changed file with 57 additions and 25 deletions.
82 changes: 57 additions & 25 deletions dandi/dandiapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,9 @@ def get_dandiset(
return RemoteDandiset(self, dandiset_id, version_id)
else:
try:
d = RemoteDandiset._make(self, self.get(f"/dandisets/{dandiset_id}/"))
d = RemoteDandiset.from_data(
self, self.get(f"/dandisets/{dandiset_id}/")
)
except requests.HTTPError as e:
if e.response.status_code == 404:
raise NotFoundError(f"No such Dandiset: {dandiset_id!r}")
Expand All @@ -485,11 +487,11 @@ def get_dandisets(self) -> Iterator["RemoteDandiset"]:
version if there is one, otherwise to the draft version.
"""
for data in self.paginate("/dandisets/"):
yield RemoteDandiset._make(self, data)
yield RemoteDandiset.from_data(self, data)

def create_dandiset(self, name: str, metadata: Dict[str, Any]) -> "RemoteDandiset":
"""Creates a Dandiset with the given name & metadata"""
return RemoteDandiset._make(
return RemoteDandiset.from_data(
self, self.post("/dandisets/", json={"name": name, "metadata": metadata})
)

Expand Down Expand Up @@ -528,7 +530,7 @@ def get_asset(self, asset_id: str) -> "BaseRemoteAsset":
method must be used instead.
"""
try:
return BaseRemoteAsset._from_metadata(self, self.get(f"/assets/{asset_id}"))
return BaseRemoteAsset.from_metadata(self, self.get(f"/assets/{asset_id}"))
except requests.HTTPError as e:
if e.response.status_code == 404:
raise NotFoundError(f"No such asset: {asset_id!r}")
Expand Down Expand Up @@ -715,11 +717,17 @@ def version_api_path(self) -> str:
return f"/dandisets/{self.identifier}/versions/{self.version_id}/"

@classmethod
def _make(cls, client: "DandiAPIClient", data: Dict[str, Any]) -> "RemoteDandiset":
def from_data(
cls, client: "DandiAPIClient", data: Dict[str, Any]
) -> "RemoteDandiset":
"""
Construct a `RemoteDandiset` instance from a `dict` returned from the
API. If the ``"most_recent_published_version"`` field is set, use that
as the Dandiset's version; otherwise, use ``"draft_version"``.
Construct a `RemoteDandiset` instance from a `DandiAPIClient` and a
`dict` of raw string fields in the same format as returned by the API.
If the ``"most_recent_published_version"`` field is set, that is used
as the Dandiset's version; otherwise, ``"draft_version"`` is used.
This is a low-level method that non-developers would normally only use
when acquiring data using means outside of this library.
"""
if data.get("most_recent_published_version") is not None:
version = Version.parse_obj(data["most_recent_published_version"])
Expand All @@ -729,14 +737,6 @@ def _make(cls, client: "DandiAPIClient", data: Dict[str, Any]) -> "RemoteDandise
client=client, identifier=data["identifier"], version=version, data=data
)

def _mkasset(self, data: Dict[str, Any]) -> "RemoteAsset":
return RemoteAsset(
client=self.client,
dandiset_id=self.identifier,
version_id=self.version_id,
**data,
)

def json_dict(self) -> Dict[str, Any]:
"""
Convert to a JSONable `dict`, omitting the ``client`` attribute and
Expand Down Expand Up @@ -903,7 +903,7 @@ def get_assets(self) -> Iterator["RemoteAsset"]:
"""Returns an iterator of all assets in this version of the Dandiset"""
try:
for a in self.client.paginate(f"{self.version_api_path}assets/"):
yield self._mkasset(a)
yield RemoteAsset.from_data(self, a)
except requests.HTTPError as e:
if e.response.status_code == 404:
raise NotFoundError(
Expand Down Expand Up @@ -937,7 +937,7 @@ def get_assets_with_path_prefix(self, path: str) -> Iterator["RemoteAsset"]:
for a in self.client.paginate(
f"{self.version_api_path}assets/", params={"path": path}
):
yield self._mkasset(a)
yield RemoteAsset.from_data(self, a)
except requests.HTTPError as e:
if e.response.status_code == 404:
raise NotFoundError(
Expand Down Expand Up @@ -1144,18 +1144,20 @@ def iter_upload_raw_asset(
yield {"status": "producing asset"}
if replace_asset is not None:
lgr.debug("%s: Replacing pre-existing asset")
a = self._mkasset(
a = RemoteAsset.from_data(
self,
self.client.put(
replace_asset.api_path,
json={"metadata": asset_metadata, "blob_id": blob_id},
)
),
)
else:
a = self._mkasset(
a = RemoteAsset.from_data(
self,
self.client.post(
f"{self.version_api_path}assets/",
json={"metadata": asset_metadata, "blob_id": blob_id},
)
),
)
lgr.info("%s: Asset successfully uploaded", asset_path)
yield {"status": "done", "asset": a}
Expand Down Expand Up @@ -1197,9 +1199,16 @@ def __str__(self) -> str:
return f"{self.client._instance_id}:assets/{self.identifier}"

@classmethod
def _from_metadata(
def from_metadata(
self, client: "DandiAPIClient", metadata: Dict[str, Any]
) -> "BaseRemoteAsset":
"""
Construct a `BaseRemoteAsset` instance from a `DandiAPIClient` and a
`dict` of raw asset metadata.
This is a low-level method that non-developers would normally only use
when acquiring data using means outside of this library.
"""
return BaseRemoteAsset(
client=client,
identifier=metadata["identifier"],
Expand Down Expand Up @@ -1365,6 +1374,29 @@ class RemoteAsset(BaseRemoteAsset):
#: The date at which the asset was last modified
modified: datetime

@classmethod
def from_data(
self,
dandiset: RemoteDandiset,
data: Dict[str, Any],
metadata: Optional[Dict[str, Any]] = None,
) -> "RemoteAsset":
"""
Construct a `RemoteAsset` instance from a `RemoteDandiset`, a `dict` of
raw data in the same format as returned by the API's pagination
endpoints, and optional raw asset metadata.
This is a low-level method that non-developers would normally only use
when acquiring data using means outside of this library.
"""
return RemoteAsset(
client=dandiset.client,
dandiset_id=dandiset.identifier,
version_id=dandiset.version_id,
**data,
_metadata=metadata,
)

@property
def api_path(self) -> str:
"""
Expand All @@ -1390,8 +1422,8 @@ def set_metadata(self, metadata: models.Asset) -> None:

def set_raw_metadata(self, metadata: Dict[str, Any]) -> None:
"""
Set the metadata for the asset to the given value and update the
`RemoteAsset` in place.
Set the metadata for the asset on the server to the given value and
update the `RemoteAsset` in place.
"""
try:
etag = metadata["digest"]["dandi:dandi-etag"]
Expand Down

0 comments on commit 04aab25

Please sign in to comment.