diff --git a/docs/formats/index.rst b/docs/formats/index.rst index b93bea2..533945a 100644 --- a/docs/formats/index.rst +++ b/docs/formats/index.rst @@ -45,3 +45,4 @@ The RedBrick SDK will export a list of :class:`redbrick.types.task.OutputTask` o annotations taxonomy + storage_method diff --git a/docs/formats/storage_method.rst b/docs/formats/storage_method.rst new file mode 100644 index 0000000..1899a88 --- /dev/null +++ b/docs/formats/storage_method.rst @@ -0,0 +1,4 @@ +Storage Method Formats +---------------------- +.. automodule:: redbrick.types.storage_method + :members: \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index fdfb0af..0f5d728 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires-python = ">=3.9,<3.14" keywords = ["redbrick"] dependencies = [ "aiohttp<4,>=3.10.5", - "altadb>=0.0.5", + "altadb>=0.0.6", "dicom2nifti<3", "inquirerpy<1", "natsort<9,>=8.0.2", diff --git a/redbrick/__init__.py b/redbrick/__init__.py index 18923b4..53620f0 100644 --- a/redbrick/__init__.py +++ b/redbrick/__init__.py @@ -10,6 +10,7 @@ from redbrick.common.context import RBContext from redbrick.common.enums import ( StorageMethod, + StorageProvider, ImportTypes, TaskEventTypes, TaskFilters, @@ -30,7 +31,7 @@ from .version_check import version_check -__version__ = "2.20.0" +__version__ = "2.20.1" # windows event loop close bug https://github.com/encode/httpx/issues/914#issuecomment-622586610 try: @@ -78,6 +79,7 @@ def _populate_context(context: RBContext) -> RBContext: SettingsRepo, ProjectRepo, WorkspaceRepo, + StorageMethodRepo, ) if context.config.debug: @@ -89,6 +91,7 @@ def _populate_context(context: RBContext) -> RBContext: context.settings = SettingsRepo(context.client) context.project = ProjectRepo(context.client) context.workspace = WorkspaceRepo(context.client) + context.storage_method = StorageMethodRepo(context.client) return context @@ -236,6 +239,7 @@ def get_project_from_profile( "version", "RBContext", "StorageMethod", + "StorageProvider", "ImportTypes", "TaxonomyTypes", "TaskTypes", diff --git a/redbrick/common/context.py b/redbrick/common/context.py index ca5de15..b819fb8 100644 --- a/redbrick/common/context.py +++ b/redbrick/common/context.py @@ -18,6 +18,7 @@ def __init__(self, api_key: str, url: str) -> None: from .settings import SettingsControllerInterface from .project import ProjectRepoInterface from .workspace import WorkspaceRepoInterface + from .storage_method import StorageMethodRepoInterface self.config = config self.client = RBClient(api_key=api_key, url=url) @@ -28,6 +29,7 @@ def __init__(self, api_key: str, url: str) -> None: self.settings: SettingsControllerInterface self.project: ProjectRepoInterface self.workspace: WorkspaceRepoInterface + self.storage_method: StorageMethodRepoInterface self._key_id: Optional[str] = None diff --git a/redbrick/common/enums.py b/redbrick/common/enums.py index 22ee91d..3f98440 100644 --- a/redbrick/common/enums.py +++ b/redbrick/common/enums.py @@ -16,6 +16,17 @@ class StorageMethod: REDBRICK = "22222222-2222-2222-2222-222222222222" +class StorageProvider(Enum): + """Storage Provider options.""" + + GCS = "GCS" + PUBLIC = "PUBLIC" + AWS_S3 = "AWS_S3" + AZURE_BLOB = "AZURE_BLOB" + REDBRICK = "REDBRICK" + ALTA_DB = "ALTA_DB" + + class TaskStates(str, Enum): """Task Status. diff --git a/redbrick/common/storage_method.py b/redbrick/common/storage_method.py new file mode 100644 index 0000000..0fcaee9 --- /dev/null +++ b/redbrick/common/storage_method.py @@ -0,0 +1,25 @@ +"""Interdace for getting information about storage methods.""" + +from abc import ABC, abstractmethod +from typing import Dict, List + +from redbrick.common.enums import StorageProvider +from redbrick.types.storage_method import StorageMethodDetails + + +class StorageMethodRepoInterface(ABC): + """Abstract interface to Storage Method APIs.""" + + @abstractmethod + def get_storage_methods(self, org_id: str) -> List[Dict]: + """Get storage methods.""" + + @abstractmethod + def create_storage_method( + self, + org_id: str, + name: str, + provider: StorageProvider, + details: StorageMethodDetails, + ) -> bool: + """Create a storage method.""" diff --git a/redbrick/repo/__init__.py b/redbrick/repo/__init__.py index f47bc76..fa5e091 100644 --- a/redbrick/repo/__init__.py +++ b/redbrick/repo/__init__.py @@ -6,3 +6,4 @@ from .settings import SettingsRepo from .project import ProjectRepo from .workspace import WorkspaceRepo +from .storage_method import StorageMethodRepo diff --git a/redbrick/repo/shards.py b/redbrick/repo/shards.py index ef6e915..f8119de 100644 --- a/redbrick/repo/shards.py +++ b/redbrick/repo/shards.py @@ -85,6 +85,52 @@ } """ +STORAGE_METHOD_SHARD = """ +orgId +storageId +name +provider +details{ + __typename + ... on S3BucketStorageDetails { + bucket + region + duration + access + roleArn + endpoint + accelerate + } + ... on GCSBucketStorageDetails { + bucket + } + ... on AzureBlobStorageDetails { + _ + } + ... on PublicStorageDetails { + _ + } + ... on RedBrickStorageDetails { + _ + } + ... on AltaDBStorageDetails { + access + host + } +} +createdBy{ + userType + userId + email + givenName + familyName + loggedInUser + idProvider +} +createdAt +deleted +""" + OLD_ATTRIBUTE_SHARD = """ name attrType diff --git a/redbrick/repo/storage_method.py b/redbrick/repo/storage_method.py new file mode 100644 index 0000000..7d9cd13 --- /dev/null +++ b/redbrick/repo/storage_method.py @@ -0,0 +1,61 @@ +"""Handlers to access APIs for storage methods.""" + +from typing import Dict, List +from redbrick.common.client import RBClient +from redbrick.common.enums import StorageProvider +from redbrick.common.storage_method import StorageMethodRepoInterface +from redbrick.repo.shards import STORAGE_METHOD_SHARD +from redbrick.types.storage_method import StorageMethodDetails + + +class StorageMethodRepo(StorageMethodRepoInterface): + """StorageMethodRepo class.""" + + def __init__(self, client: RBClient): + """Construct StorageMethodRepo.""" + self.client = client + + def get_storage_methods(self, org_id: str) -> List[Dict]: + """Get storage methods.""" + query = f""" + query listStorageMethodsSDK($orgId: UUID!) {{ + storageMethods(orgId: $orgId) {{ + {STORAGE_METHOD_SHARD} + }} + }} + """ + variables = {"orgId": org_id} + response = self.client.execute_query(query, variables) + return response["storageMethods"] + + def create_storage_method( + self, + org_id: str, + name: str, + provider: StorageProvider, + details: StorageMethodDetails, + ) -> bool: + """Create a storage method.""" + query = """ + mutation createStorageSDK($orgId: UUID!, $name: String!, $provider: PROVIDER!, $details: StorageDetailsInput){ + createStorage(orgId: $orgId, name: $name, provider: $provider, details: $details){ + ok + } + } + """ + provider_map = { + StorageProvider.ALTA_DB: "altaDb", + StorageProvider.AWS_S3: "s3Bucket", + StorageProvider.AZURE_BLOB: "azureBucket", + StorageProvider.GCS: "gcsBucket", + } + variables = { + "orgId": org_id, + "name": name, + "provider": provider.value, + "details": { + provider_map[provider]: details, + }, + } + response = self.client.execute_query(query, variables) + return response["createStorage"]["ok"] diff --git a/redbrick/types/storage_method.py b/redbrick/types/storage_method.py new file mode 100644 index 0000000..167f6d6 --- /dev/null +++ b/redbrick/types/storage_method.py @@ -0,0 +1,81 @@ +"""Storage Method Types""" + +from typing import TypedDict, Union +from typing_extensions import NotRequired + + +class InputAWSS3StorageMethodDetails(TypedDict): + """AWS S3 Storage Method Type. + + Contains the necessary information to access a user's AWS S3 bucket. + """ + + #: The name of the bucket + bucket: str + + #: The region of the bucket + region: str + + #: The duration of the session + duration: NotRequired[int] + + #: The access key (AWS_ACCESS_KEY_ID) + access: NotRequired[str] + + #: The secret key (AWS_SECRET_ACCESS_KEY) + secret: NotRequired[str] + + # session creds + + #: The role ARN + roleArn: NotRequired[str] + + #: The role external ID + roleExternalId: NotRequired[str] + + #: The endpoint + endpoint: NotRequired[str] + + #: Whether to use S3 transfer acceleration + accelerate: NotRequired[bool] + + +class InputGCSStorageMethodDetails(TypedDict): + """Storage information for DataPoints in a user's Google Cloud bucket.""" + + #: The name of the bucket + bucket: str + + #: The service account JSON as a string + serviceAccount: str + + +class InputAzureBlobStorageMethodDetails(TypedDict): + """Azure Blob Storage Method Type.""" + + #: The connection string + connectionString: NotRequired[str] + + #: The SAS URL + sasUrl: NotRequired[str] + + +class InputAltaDBStorageMethodDetails(TypedDict): + """AltaDB Storage Method Type.""" + + #: The access key + access: str + + #: The secret key + secret: str + + #: The host (backend URL) + host: NotRequired[str] + + +StorageMethodDetails = Union[ + InputAWSS3StorageMethodDetails, + InputGCSStorageMethodDetails, + InputAzureBlobStorageMethodDetails, + InputAltaDBStorageMethodDetails, +]