From 34ff697759df8f953c8a35efc3939f4263bc38e3 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 9 Nov 2023 16:56:48 -0500 Subject: [PATCH] Convert dandiarchive URL classes from pydantic models to dataclasses --- dandi/consts.py | 5 +---- dandi/dandiarchive.py | 23 ++++++++++++++++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/dandi/consts.py b/dandi/consts.py index 23964483f..d8e47350d 100644 --- a/dandi/consts.py +++ b/dandi/consts.py @@ -4,7 +4,6 @@ from dataclasses import dataclass from enum import Enum import os -from typing import Union #: A list of metadata fields which dandi extracts from .nwb files. #: Additional fields (such as ``number_of_*``) might be added by @@ -99,9 +98,7 @@ class EmbargoStatus(Enum): @dataclass(frozen=True) class DandiInstance: name: str - # This class is used as an attribute of ParsedDandiURL, a Pydantic class, - # and thus we need non-future annotations: - gui: Union[str, None] + gui: str | None api: str @property diff --git a/dandi/dandiarchive.py b/dandi/dandiarchive.py index fd4736da9..d0f6d6e6d 100644 --- a/dandi/dandiarchive.py +++ b/dandi/dandiarchive.py @@ -30,13 +30,14 @@ from abc import ABC, abstractmethod from collections.abc import Iterable, Iterator from contextlib import contextmanager +from dataclasses import dataclass, field import posixpath import re from time import sleep -from typing import Any, Union +from typing import Any from urllib.parse import unquote as urlunquote -from pydantic import AnyHttpUrl, BaseModel, parse_obj_as +from pydantic import AnyHttpUrl, parse_obj_as import requests from . import get_logger @@ -55,7 +56,8 @@ lgr = get_logger() -class ParsedDandiURL(ABC, BaseModel): +@dataclass +class ParsedDandiURL(ABC): """ Parsed representation of a URL pointing to a Dandi Archive resource (Dandiset or asset(s)). Subclasses must implement `get_assets()`. @@ -70,11 +72,11 @@ class ParsedDandiURL(ABC, BaseModel): #: The Dandi Archive instance that the URL points to instance: DandiInstance #: The ID of the Dandiset given in the URL - dandiset_id: Union[str, None] + dandiset_id: str | None #: The version of the Dandiset, if specified. If this is not set, the #: version will be defaulted using the rules described under #: `DandiAPIClient.get_dandiset()`. - version_id: Union[str, None] = None + version_id: str | None @property def api_url(self) -> AnyHttpUrl: @@ -215,6 +217,7 @@ def is_under_download_path(self, path: str) -> bool: ... +@dataclass class DandisetURL(ParsedDandiURL): """ Parsed from a URL that only refers to a Dandiset (possibly with a version) @@ -236,6 +239,7 @@ def is_under_download_path(self, path: str) -> bool: return True +@dataclass class SingleAssetURL(ParsedDandiURL): """Superclass for parsed URLs that refer to a single asset""" @@ -248,6 +252,7 @@ def is_under_download_path(self, path: str) -> bool: ) +@dataclass class MultiAssetURL(ParsedDandiURL): """Superclass for parsed URLs that refer to multiple assets""" @@ -264,12 +269,15 @@ def is_under_download_path(self, path: str) -> bool: return path.startswith(self.path) +@dataclass class BaseAssetIDURL(SingleAssetURL): """ Parsed from a URL that refers to an asset by ID and does not include the Dandiset ID """ + dandiset_id: None = field(init=False, default=None) + version_id: None = field(init=False, default=None) asset_id: str def get_assets( @@ -313,6 +321,7 @@ def navigate( yield (client, dandiset, assets) +@dataclass class AssetIDURL(SingleAssetURL): """ Parsed from a URL that refers to an asset by ID and includes the Dandiset ID @@ -338,6 +347,7 @@ def get_asset_ids(self, client: DandiAPIClient) -> Iterator[str]: yield self.asset_id +@dataclass class AssetPathPrefixURL(MultiAssetURL): """ Parsed from a URL that refers to a collection of assets by path prefix @@ -366,6 +376,7 @@ def get_assets( raise NotFoundError(f"No assets found with path prefix {self.path!r}") +@dataclass class AssetItemURL(SingleAssetURL): """Parsed from a URL that refers to a specific asset by path""" @@ -414,6 +425,7 @@ def get_assets( ) +@dataclass class AssetFolderURL(MultiAssetURL): """ Parsed from a URL that refers to a collection of assets by folder path @@ -446,6 +458,7 @@ def get_assets( raise NotFoundError(f"No assets found under folder {path!r}") +@dataclass class AssetGlobURL(MultiAssetURL): """ .. versionadded:: 0.54.0