diff --git a/src/provenaclient/clients/prov_client.py b/src/provenaclient/clients/prov_client.py index b7e699d..cf285cf 100644 --- a/src/provenaclient/clients/prov_client.py +++ b/src/provenaclient/clients/prov_client.py @@ -85,8 +85,6 @@ async def generate_config_file(self, required_only: bool) -> str: ---------- required_only : bool, optional By default True - file_name : str, optional - The filename you want to have, by default DEFAULT_CONFIG_FILE_NAME (prov-api.env) """ response = await validated_get_request( diff --git a/src/provenaclient/clients/registry_client.py b/src/provenaclient/clients/registry_client.py index 6186c22..7502ee8 100644 --- a/src/provenaclient/clients/registry_client.py +++ b/src/provenaclient/clients/registry_client.py @@ -11,16 +11,19 @@ Date By Comments ---------- --- --------------------------------------------------------- +28-06-2024 | Parth Kulkarni | Completion of L2 Interface of Registry with General, Admin and Other endpoints. 18-06-2024 | Peter Baker | Initial structure setup to help dispatch into the various sub types in the L3. ''' from provenaclient.auth.manager import AuthManager +from provenaclient.models.general import HealthCheckResponse from provenaclient.utils.config import Config from enum import Enum from provenaclient.utils.helpers import * from provenaclient.clients.client_helpers import * from provenaclient.utils.registry_endpoints import * from ProvenaInterfaces.RegistryModels import * +from ProvenaInterfaces.RegistryAPI import * class GenericRegistryEndpoints(str, Enum): @@ -41,10 +44,9 @@ class RegistryAdminEndpoints(str, Enum): POST_ADMIN_IMPORT = "/admin/import" POST_ADMIN_RESTORE_FROM_TABLE = "/admin/restore_from_table" - class RegistryAdminClient(ClientService): def __init__(self, auth: AuthManager, config: Config) -> None: - """Initialises the RegistryClient with authentication and configuration. + """Initialises the RegistryAdminClient with authentication and configuration. Parameters ---------- @@ -58,12 +60,233 @@ def __init__(self, auth: AuthManager, config: Config) -> None: def _build_endpoint(self, endpoint: RegistryAdminEndpoints) -> str: return f"{self._config.registry_api_endpoint}{endpoint.value}" + + def _build_subtype_endpoint(self, action: RegistryAction, item_subtype: ItemSubType) -> str: + return subtype_action_to_endpoint( + base=self._config.registry_api_endpoint, + action=action, + item_subtype=item_subtype + ) + + async def export_items(self) -> RegistryExportResponse: + """ + Exports all items from the registry. + + Returns + ------- + RegistryExportResponse + The response containing the exported items. + """ + endpoint = self._build_endpoint(RegistryAdminEndpoints.GET_ADMIN_EXPORT) + + return await parsed_get_request_with_status( + client=self, + url=endpoint, + params=None, + error_message="Failed to export all items from the registry!", + model=RegistryExportResponse + ) + + async def import_items(self, registry_import_request: RegistryImportRequest) -> RegistryImportResponse: + """ + Imports items into the registry. + + Parameters + ---------- + registry_import_request : RegistryImportRequest + The import request containing the items to import. + + Returns + ------- + RegistryImportResponse + The response containing the result of the import operation. + """ + endpoint = self._build_endpoint(RegistryAdminEndpoints.POST_ADMIN_IMPORT) + + return await parsed_post_request_with_status( + client=self, + url=endpoint, + params=None, + json_body=py_to_dict(registry_import_request), + error_message="Failed to import items into the registry!", + model=RegistryImportResponse + ) + + async def restore_items_from_dynamo_table(self, restore_request: RegistryRestoreRequest) -> RegistryImportResponse: + """ + Restores items from a DynamoDB table into the registry. + + Parameters + ---------- + restore_request : RegistryRestoreRequest + The restore request containing the details for restoration. + + Returns + ------- + RegistryImportResponse + The response containing the result of the restore operation. + """ + endpoint = self._build_endpoint(RegistryAdminEndpoints.POST_ADMIN_RESTORE_FROM_TABLE) + + return await parsed_post_request_with_status( + client=self, + url=endpoint, + params=None, + json_body=py_to_dict(restore_request), + error_message="Failed to restore items from DynamoDB table into the registry!", + model=RegistryImportResponse + ) + + async def generate_config_file(self, required_only: bool) -> str: + """ + Generates a nicely formatted .env file of the current required/non-supplied properties. + + Used to quickly bootstrap a local environment or to understand currently deployed API. + + Parameters + ---------- + required_only : bool + Whether to include only required properties. By default True. + + Returns + ------- + str + The generated .env file content. + """ + response = await validated_get_request( + client=self, + url=self._build_endpoint(RegistryAdminEndpoints.GET_ADMIN_CONFIG), + error_message="Failed to generate config file", + params={"required_only": required_only}, + ) + + return response.text + + async def delete_item(self, id: str, item_subtype: ItemSubType) -> StatusResponse: + """ + Deletes an item from the registry. + + Parameters + ---------- + id : str + The ID of the item to delete. + item_subtype : ItemSubType + The subtype of the item to delete. + + Returns + ------- + StatusResponse + The status response indicating the result of the deletion. + """ + endpoint = self._build_subtype_endpoint( + action=RegistryAction.DELETE, item_subtype=item_subtype + ) + + return await parsed_delete_request_with_status( + client=self, + params={'id': id}, + error_message=f"Failed to delete item with id {id} and subtype {item_subtype}", + model=StatusResponse, + url=endpoint + ) + +class RegistryGeneralClient(ClientService): + + def __init__(self, auth: AuthManager, config: Config) -> None: + """Initialises the RegistryGeneralClient with authentication and configuration. + + Parameters + ---------- + auth: AuthManager + An abstract interface containing the user's requested auth flow method. + config: Config + A config object which contains information related to the Provena instance. + """ + self._auth = auth + self._config = config + + def _build_subtype_endpoint(self, action: RegistryAction, item_subtype: ItemSubType) -> str: + return subtype_action_to_endpoint( + base=self._config.registry_api_endpoint, + action=action, + item_subtype=item_subtype + ) + + def _build_general_endpoint(self, endpoint: GenericRegistryEndpoints) -> str: + return f"{self._config.registry_api_endpoint}{endpoint.value}" + + async def list_general_registry_items(self, general_list_request: GeneralListRequest) -> PaginatedListResponse: + """ + Lists general registry items based on filter criteria. + + Parameters + ---------- + general_list_request : GeneralListRequest + The request containing filter and sort criteria. + + Returns + ------- + PaginatedListResponse + The response containing the paginated list of registry items. + """ + endpoint = self._build_general_endpoint(endpoint=GenericRegistryEndpoints.POST_REGISTRY_GENERAL_LIST) + + return await parsed_post_request_with_status( + client=self, + url=endpoint, + params=None, + json_body=py_to_dict(general_list_request), + error_message=f"General list fetch failed!", + model=PaginatedListResponse + ) + + async def general_fetch_item(self, id: str) -> UntypedFetchResponse: + """ + Fetches a general item from the registry. + + Parameters + ---------- + id : str + The ID of the item to fetch. + + Returns + ------- + UntypedFetchResponse + The fetch response containing the item details. + """ + endpoint = self._build_general_endpoint(endpoint=GenericRegistryEndpoints.GET_REGISTRY_GENERAL_FETCH) + + return await parsed_get_request_with_status( + client=self, + url=endpoint, + params={"id": id}, + error_message=f"Failed to fetch item with id {id} from general registry!", + model=UntypedFetchResponse + ) + + async def get_current_provena_version(self) -> VersionResponse: + """ + Gets the current Provena version. + Returns + ------- + VersionResponse + The response containing the current Provena version. + """ + endpoint = self._build_general_endpoint(endpoint=GenericRegistryEndpoints.GET_REGISTRY_GENERAL_ABOUT_VERSION) + + return await parsed_get_request( + client=self, + url=endpoint, + params=None, + error_message="Failed to fetch the current Provena version of your instance.", + model=VersionResponse + ) -# L2 interface. class RegistryClient(ClientService): # Sub clients admin: RegistryAdminClient + general: RegistryGeneralClient def __init__(self, auth: AuthManager, config: Config) -> None: """Initialises the RegistryClient with authentication and configuration. @@ -80,6 +303,7 @@ def __init__(self, auth: AuthManager, config: Config) -> None: # Sub clients self.admin = RegistryAdminClient(auth=auth, config=config) + self.general = RegistryGeneralClient(auth=auth, config=config) # Function to get the endpoint URL def _build_subtype_endpoint(self, action: RegistryAction, item_subtype: ItemSubType) -> str: @@ -92,18 +316,42 @@ def _build_subtype_endpoint(self, action: RegistryAction, item_subtype: ItemSubT def _build_general_endpoint(self, endpoint: GenericRegistryEndpoints) -> str: return f"{self._config.registry_api_endpoint}{endpoint.value}" + async def get_health_check(self) -> HealthCheckResponse: + """ + Health check the API + + Returns + ------- + HealthCheckResponse + Response + """ + return await parsed_get_request( + client=self, + url=self._build_general_endpoint(GenericRegistryEndpoints.GET_HEALTH_CHECK), + error_message="Health check failed!", + params={}, + model=HealthCheckResponse + ) + async def fetch_item(self, id: str, item_subtype: ItemSubType, fetch_response_model: Type[BaseModelType], seed_allowed: Optional[bool] = None) -> BaseModelType: """ Ascertains the correct endpoint based on the subtype provided, then runs the fetch operation, parsing the data as the specified model. - Args: - id (str): The id of the item to fetch. - item_subtype (ItemSubType): The subtype of the item to fetch. - fetch_response_model (Type[BaseModelType]): The response pydantic model to parse as e.g. OrganisationFetchResponse - seed_allowed (Optional[bool], optional): Should the endpoint throw an error if the item is a seed item? Defaults to None. + Parameters + ---------- + id : str + The id of the item to fetch. + item_subtype : ItemSubType + The subtype of the item to fetch. + fetch_response_model : Type[BaseModelType] + The response pydantic model to parse as e.g. OrganisationFetchResponse + seed_allowed : Optional[bool], optional + Should the endpoint throw an error if the item is a seed item? Defaults to None. - Returns: - BaseModelType: _description_ + Returns + ------- + BaseModelType + The fetch response parsed as the specified model. """ # determine endpoint endpoint = self._build_subtype_endpoint( @@ -122,15 +370,23 @@ async def update_item(self, id: str, reason: Optional[str], item_subtype: ItemSu """ Ascertains the correct endpoint then runs the update operation on an existing item by providing new domain info. - Args: - id (str): The id of item to update. - reason (Optional[str]): The reason for updating, if any - item_subtype (ItemSubType): The subtype to update - domain_info (DomainInfoBase): The domain info to replace existing item with - update_response_model (Type[BaseModelType]): The response model to parse e.g. StatusResponse + Parameters + ---------- + id : str + The id of item to update. + reason : Optional[str] + The reason for updating, if any + item_subtype : ItemSubType + The subtype to update + domain_info : DomainInfoBase + The domain info to replace existing item with + update_response_model : Type[BaseModelType] + The response model to parse e.g. StatusResponse - Returns: - BaseModelType: The response model parsed + Returns + ------- + BaseModelType + The response model parsed """ # determine endpoint endpoint = self._build_subtype_endpoint( @@ -145,3 +401,465 @@ async def update_item(self, id: str, reason: Optional[str], item_subtype: ItemSu model=update_response_model, url=endpoint, ) + + async def list_items(self, list_items_payload: GeneralListRequest, item_subtype: ItemSubType, update_model_response: Type[BaseModelType]) -> BaseModelType: + """ + Lists items within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + The request containing filter and sort criteria. + item_subtype : ItemSubType + The subtype of the items to list. + update_model_response : Type[BaseModelType] + The response model to parse. + + Returns + ------- + BaseModelType + The response containing the list of items. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.LIST, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_post_request_with_status( + client=self, + params=None, + json_body=py_to_dict(list_items_payload), + error_message=f"Failed to list items for {item_subtype}", + model=update_model_response, + url=endpoint + ) + + async def seed_item(self, item_subtype: ItemSubType, seed_model_response: Type[BaseModelType]) -> BaseModelType: + """ + Seeds an item in the registry. + + Parameters + ---------- + item_subtype : ItemSubType + The subtype of the item to seed. + seed_model_response : Type[BaseModelType] + The response model to parse. + + Returns + ------- + BaseModelType + The response containing the details of the seeded item. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.SEED, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_post_request_with_status( + client=self, + params=None, + json_body=None, + error_message=f"Failed to seed items for {item_subtype}", + model=seed_model_response, + url=endpoint + ) + + async def revert_item(self, revert_request: ItemRevertRequest, item_subtype: ItemSubType) -> ItemRevertResponse: + """ + Reverts an item in the registry. + + Parameters + ---------- + revert_request : ItemRevertRequest + The revert request. + item_subtype : ItemSubType + The subtype of the item to revert. + + Returns + ------- + ItemRevertResponse + The revert response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.REVERT, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_put_request_with_status( + client=self, + params=None, + json_body=py_to_dict(revert_request), + error_message=f"Failed to revert items for {item_subtype}", + model=ItemRevertResponse, + url=endpoint + ) + + async def create_item(self, create_item_request: DomainInfoBase, item_subtype: ItemSubType, create_response_model: Type[BaseModelType]) -> BaseModelType: + """ + Creates an item in the registry. + + Parameters + ---------- + create_item_request : DomainInfoBase + The domain information required to create the item. + item_subtype : ItemSubType + The subtype of the item to create. + create_response_model : Type[BaseModelType] + The response model to parse. + + Returns + ------- + BaseModelType + The response containing the details of the created item. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.CREATE, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_post_request_with_status( + client=self, + params=None, + json_body=py_to_dict(create_item_request), + error_message=f"Failed to create items for {item_subtype}", + model=create_response_model, + url=endpoint + ) + + async def get_schema(self, item_subtype: ItemSubType) -> JsonSchemaResponse: + """ + Gets the schema for the item subtype. + + Parameters + ---------- + item_subtype : ItemSubType + The subtype of the item to get the schema for. + + Returns + ------- + JsonSchemaResponse + The JSON schema response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.SCHEMA, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_get_request_with_status( + client=self, + params=None, + error_message=f"Failed to get schema for {item_subtype}", + model=JsonSchemaResponse, + url=endpoint + ) + + async def validate_item(self, validate_request: DomainInfoBase, item_subtype: ItemSubType) -> StatusResponse: + """ + Validates an item in the registry. + + Parameters + ---------- + validate_request : DomainInfoBase + The domain information of the item to be validated. + item_subtype : ItemSubType + The subtype of the item to validate. + + Returns + ------- + StatusResponse + The status response indicating the result of the validation. + """ + + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.VALIDATE, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_post_request_with_status( + client=self, + params=None, + json_body=py_to_dict(validate_request), + error_message=f"Failed to validate item for {item_subtype}", + model=StatusResponse, + url=endpoint + ) + + async def evaluate_auth_access(self, id: str, item_subtype: ItemSubType) -> DescribeAccessResponse: + """ + Evaluates the auth access for an item. + + Parameters + ---------- + id : str + The ID of the item to evaluate auth access for. + item_subtype : ItemSubType + The subtype of the item to evaluate auth access for. + + Returns + ------- + DescribeAccessResponse + The describe access response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.AUTH_EVALUATE, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_get_request( + client=self, + params={"id": id}, + error_message=f"Failed to evaluate auth access for {item_subtype}", + model=DescribeAccessResponse, + url=endpoint + ) + + async def get_auth_configuration(self, id: str, item_subtype: ItemSubType) -> AccessSettings: + """ + Gets the auth configuration for an item. + + Parameters + ---------- + id : str + The ID of the item to get auth configuration for. + item_subtype : ItemSubType + The subtype of the item to get auth configuration for. + + Returns + ------- + AccessSettings + The access settings. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.AUTH_CONFIGURATION, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_get_request( + client=self, + params={"id": id}, + error_message=f"Failed to get auth config for {item_subtype}", + model=AccessSettings, + url=endpoint + ) + + async def modify_auth_configuration(self, id: str, auth_change_request: AccessSettings, item_subtype: ItemSubType) -> StatusResponse: + """ + Modifies the auth configuration for an item. + + Parameters + ---------- + id : str + The ID of the item to modify auth configuration for. + auth_change_request : AccessSettings + The auth change request. + item_subtype : ItemSubType + The subtype of the item to modify auth configuration for. + + Returns + ------- + StatusResponse + The status response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.AUTH_CONFIGURATION, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_put_request_with_status( + client=self, + params={"id": id}, + json_body=py_to_dict(auth_change_request), + error_message=f"Failed to modify auth config for {item_subtype}", + model=StatusResponse, + url=endpoint + ) + + async def get_auth_roles(self, item_subtype: ItemSubType) -> AuthRolesResponse: + """ + Gets the auth roles for the item subtype. + + Parameters + ---------- + item_subtype : ItemSubType + The subtype of the item to get auth roles for. + + Returns + ------- + AuthRolesResponse + The auth roles response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.AUTH_ROLES, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_get_request( + client=self, + params=None, + error_message=f"Failed to get auth roles for {item_subtype}", + model=AuthRolesResponse, + url=endpoint + ) + + async def lock_resource(self, lock_resource_request: LockChangeRequest, item_subtype: ItemSubType) -> StatusResponse: + """ + Locks a resource in the registry. + + Parameters + ---------- + lock_resource_request : LockChangeRequest + The lock resource request. + item_subtype : ItemSubType + The subtype of the resource to lock. + + Returns + ------- + StatusResponse + The status response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.LOCK, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_put_request_with_status( + client=self, + params=None, + json_body=py_to_dict(lock_resource_request), + error_message=f"Failed to lock resource for {item_subtype}", + model=StatusResponse, + url=endpoint + ) + + async def unlock_resource(self, unlock_resource_request: LockChangeRequest, item_subtype: ItemSubType) -> StatusResponse: + """ + Unlocks a resource in the registry. + + Parameters + ---------- + unlock_resource_request : LockChangeRequest + The unlock resource request. + item_subtype : ItemSubType + The subtype of the resource to unlock. + + Returns + ------- + StatusResponse + The status response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.UNLOCK, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_put_request_with_status( + client=self, + params=None, + json_body=py_to_dict(unlock_resource_request), + error_message=f"Failed to unlock resource for {item_subtype}", + model=StatusResponse, + url=endpoint + ) + + async def get_lock_history(self, handle_id: str, item_subtype: ItemSubType) -> LockHistoryResponse: + """ + Gets the lock history for an item. + + Parameters + ---------- + handle_id : str + The handle ID of the item to get lock history for. + item_subtype : ItemSubType + The subtype of the item to get lock history for. + + Returns + ------- + LockHistoryResponse + The lock history response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.LOCK_HISTORY, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_get_request_with_status( + client=self, + params={"id": handle_id}, + error_message=f"Failed to get lock history for id {handle_id} for subtype {item_subtype}", + model=LockHistoryResponse, + url=endpoint + ) + + async def get_lock_status(self, id: str, item_subtype: ItemSubType) -> LockStatusResponse: + """ + Gets the lock status for an item. + + Parameters + ---------- + id : str + The item ID. + item_subtype : ItemSubType + The subtype of the item to get lock status for. + + Returns + ------- + LockStatusResponse + The lock status response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.LOCK_HISTORY, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_get_request_with_status( + client=self, + params={"id": id}, + error_message=f"Failed to get lock status for id {id} with subtype {item_subtype}", + model=LockStatusResponse, + url=endpoint + ) + + async def version(self, version_request: VersionRequest, item_subtype: ItemSubType) -> VersionResponse: + """ + Versions an item in the registry. + + Parameters + ---------- + version_request : VersionRequest + The version request containing the version details. + item_subtype : ItemSubType + The subtype of the item to version. + + Returns + ------- + VersionResponse + The version response. + """ + # determine endpoint + endpoint = self._build_subtype_endpoint( + action=RegistryAction.VERSION, item_subtype=item_subtype + ) + + # fetch item from the subtype specific endpoint + return await parsed_post_request( + client=self, + params=None, + json_body=py_to_dict(version_request), + error_message=f"Failed to complete versioning for subtype {item_subtype}", + model=VersionResponse, + url=endpoint + ) \ No newline at end of file diff --git a/src/provenaclient/modules/prov.py b/src/provenaclient/modules/prov.py index 00f23ea..8f414bc 100644 --- a/src/provenaclient/modules/prov.py +++ b/src/provenaclient/modules/prov.py @@ -51,7 +51,7 @@ def __init__(self, auth: AuthManager, config: Config, prov_api_client: ProvClien self._auth = auth self._config = config - # Clients related to the datastore scoped as private. + # Clients related to the prov_api scoped as private. self._prov_api_client = prov_api_client async def generate_config_file(self, required_only: bool = True, file_path: Optional[str] = None, write_to_file: bool = False) -> str: diff --git a/src/provenaclient/modules/registry.py b/src/provenaclient/modules/registry.py index 91958f7..2884b88 100644 --- a/src/provenaclient/modules/registry.py +++ b/src/provenaclient/modules/registry.py @@ -10,27 +10,35 @@ HISTORY: Date By Comments ---------- --- --------------------------------------------------------- - +28-06-2024 | Parth Kulkarni | Completion of Registry L3 interface with General, Admin and Other endpoints. 18-06-2024 | Peter Baker | Initial proof of concept with fetch/update methods from L2. Sub Modules for each subtype. + + ''' from provenaclient.auth.manager import AuthManager +from provenaclient.models.general import HealthCheckResponse from provenaclient.utils.config import Config from provenaclient.modules.module_helpers import * from provenaclient.clients import RegistryClient from ProvenaInterfaces.RegistryModels import * from ProvenaInterfaces.RegistryAPI import * from typing import Optional +from provenaclient.utils.helpers import convert_to_item_subtype, write_file_helper, get_and_validate_file_path -# L3 interface. +DEFAULT_CONFIG_FILE_NAME = "registry-api.env" -class OrganisationClient(ModuleService): +# L3 interface. + +class RegistryAdminClient(ModuleService): _registry_client: RegistryClient def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: """ + Admin sub module of the Registry API providing functionality + for the admin endpoints. Parameters ---------- @@ -39,14 +47,372 @@ def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryC method. config : Config A config object which contains information related to the Provena - instance. + instance. + auth_client: AuthClient + The instantiated auth client """ - # Module service self._auth = auth self._config = config - # Clients related to the registry scoped as private. + # Clients related to the registry_api scoped as private. + self._registry_client = registry_client + + + async def export_items(self) -> RegistryExportResponse: + """Provides a mechanism for admins to dump the current contents of the registry table without any validation/parsing. + + Returns + ------- + RegistryExportResponse + A status response including items in the payload + """ + + return await self._registry_client.admin.export_items() + + async def import_items(self, registry_import_request: RegistryImportRequest) -> RegistryImportResponse: + """This admin only endpoint enables rapid restoration of items in into the registry table. + + Parameters + ---------- + registry_import_request : RegistryImportRequest + Contains the import mode, more info can be found on API docs. + + Returns + ------- + RegistryImportResponse + Returns an import response which includes status + statistics. + """ + + return await self._registry_client.admin.import_items( + registry_import_request=registry_import_request + ) + + async def restore_items_from_dynamo_table(self, restore_request: RegistryRestoreRequest) -> RegistryImportResponse: + """Provides an admin only mechanism for copying/restoring the contents from another dynamoDB table into the currently active registry table. + This endpoint does not create any new tables - it just uses the items from a restored table (e.g. from a backup) as inputs to an import operation + against the current registry table. + + Parameters + ---------- + restore_request : RegistryRestoreRequest + The restore request settings - these will be used when propagating the items from the external table. + + Returns + ------- + RegistryImportResponse + Returns information about the import, including status and statistics. + """ + + return await self._registry_client.admin.restore_items_from_dynamo_table( + restore_request=restore_request + ) + + async def generate_config_file(self, required_only: bool = True, file_path: Optional[str] = None, write_to_file: bool = False) -> str: + """Generates a nicely formatted .env file of the current required/non supplied properties + Used to quickly bootstrap a local environment or to understand currently deployed API. + + Parameters + ---------- + required_only : bool, optional + By default True + file_path: str, optional + The path you want to save the config file at WITH the file name. If you don't specify a path + this will be saved in a relative directory. + write_to_file: bool, By default False + A boolean flag to indicate whether you want to save the config response to a file + or not. + + Returns + ---------- + str: Response containing the config text. + + """ + + file_path = get_and_validate_file_path(file_path=file_path, write_to_file=write_to_file, default_file_name=DEFAULT_CONFIG_FILE_NAME) + + config_text: str = await self._registry_client.admin.generate_config_file(required_only=required_only) + + if config_text is None: + raise ValueError(f"No data returned for generate config file endpoint.") + + # Write to file if config text is not None, write to file is True and file path is not None. + if write_to_file: + if file_path is None: + raise ValueError("File path is not set for writing the CSV.") + write_file_helper(file_path=file_path, content=config_text) + + return config_text + + async def delete(self, id: str, item_subtype: Optional[ItemSubType] = None) -> StatusResponse: + """Admin only endpoint for deleting item from registry. USE CAREFULLY! + + Parameters + ---------- + id : str + ID of entity/item you want to delete. + item_subtype : Optional[ItemSubType] + Subtype of item you want to delete (E.g ORGANISATION, PERSON, CREATE) + If not provided, it will be fetched from the registry. + + Returns + ------- + StatusResponse + Response indicating the success/failure of your request. + """ + + if item_subtype is None: + fetch_item = await self._registry_client.general.general_fetch_item(id=id) + + if fetch_item.item: + item_subtype_str: Optional[str] = fetch_item.item.get("item_subtype") + item_subtype = convert_to_item_subtype(item_subtype_str) + else: + raise ValueError("Item not found") + + return await self._registry_client.admin.delete_item( + id = id, + item_subtype=item_subtype + ) + + +class RegistryBaseClass(ModuleService): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient, item_subtype: ItemSubType) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow method. + config : Config + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. + item_subtype : ItemSubType + The subtype of the item (e.g., ORGANISATION, PERSON). + """ + self._auth = auth + self._config = config self._registry_client = registry_client + self.item_subtype = item_subtype + + + async def admin_delete(self, id: str) -> StatusResponse: + """Admin only endpoint for deleting item from registry. USE CAREFULLY! + + Parameters + ---------- + id : str + ID of entity/item you want to delete. + + Returns + ------- + StatusResponse + Response indicating the success/failure of your request. + """ + + return await self._registry_client.admin.delete_item( + id = id, + item_subtype=self.item_subtype + ) + + async def revert_item(self, revert_request: ItemRevertRequest) -> ItemRevertResponse: + """ + Reverts an item in the registry based on item subtype. + + Parameters + ---------- + revert_request : ItemRevertRequest + The revert request + + Returns + ------- + ItemRevertResponse + The revert response + """ + return await self._registry_client.revert_item( + revert_request=revert_request, + item_subtype=self.item_subtype + ) + + async def get_schema(self) -> JsonSchemaResponse: + """ + Gets the schema for the item subtype + + Returns + ------- + JsonSchemaResponse + The JSON schema response + """ + return await self._registry_client.get_schema( + item_subtype=self.item_subtype + ) + + async def evaluate_auth_access(self, id: str) -> DescribeAccessResponse: + """ + Evaluates the auth access for an item based on item subtype. + + Parameters + ---------- + id : str + The item ID + + Returns + ------- + DescribeAccessResponse + The describe access response + """ + return await self._registry_client.evaluate_auth_access( + id=id, + item_subtype=self.item_subtype + ) + + async def get_auth_configuration(self, id: str) -> AccessSettings: + """ + Gets the auth configuration for an item based on item subtype. + + Parameters + ---------- + id : str + The item ID + + Returns + ------- + AccessSettings + The access settings + """ + return await self._registry_client.get_auth_configuration( + id=id, + item_subtype=self.item_subtype + ) + + async def modify_auth_configuration(self, id: str, auth_change_request: AccessSettings) -> StatusResponse: + """ + Modifies the auth configuration for an item based on item subtype. + + Parameters + ---------- + id : str + The item ID + auth_change_request : AccessSettings + The auth change request + + Returns + ------- + StatusResponse + The status response + """ + return await self._registry_client.modify_auth_configuration( + id=id, + auth_change_request=auth_change_request, + item_subtype=self.item_subtype + ) + + async def get_auth_roles(self) -> AuthRolesResponse: + """ + Gets the auth roles for the item subtype. + + Returns + ------- + AuthRolesResponse + The auth roles response + """ + return await self._registry_client.get_auth_roles( + item_subtype=self.item_subtype + ) + + async def lock_resource(self, lock_resource_request: LockChangeRequest) -> StatusResponse: + """ + Locks a resource in the registry based on item subtype. + + Parameters + ---------- + lock_resource_request : LockChangeRequest + The lock resource request + + Returns + ------- + StatusResponse + The status response + """ + return await self._registry_client.lock_resource( + lock_resource_request=lock_resource_request, + item_subtype=self.item_subtype + ) + + async def unlock_resource(self, unlock_resource_request: LockChangeRequest) -> StatusResponse: + """ + Unlocks a resource in the registry based on item subtype. + + Parameters + ---------- + unlock_resource_request : LockChangeRequest + The unlock resource request + + Returns + ------- + StatusResponse + The status response + """ + return await self._registry_client.unlock_resource( + unlock_resource_request=unlock_resource_request, + item_subtype=self.item_subtype + ) + + async def get_lock_history(self, id: str) -> LockHistoryResponse: + """ + Gets the lock history for an item based on item subtype. + + Parameters + ---------- + id : str + The item ID + + Returns + ------- + LockHistoryResponse + The lock history response + """ + return await self._registry_client.get_lock_history( + handle_id=id, + item_subtype=self.item_subtype + ) + + async def get_lock_status(self, id: str) -> LockStatusResponse: + """ + Gets the lock status for an item based on item subtype. + + Parameters + ---------- + id : str + The item ID + + Returns + ------- + LockStatusResponse + The lock status response + """ + return await self._registry_client.get_lock_status( + id=id, + item_subtype=self.item_subtype + ) + + +class OrganisationClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow method. + config : Config + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. + """ + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.ORGANISATION) async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> OrganisationFetchResponse: """ @@ -61,7 +427,7 @@ async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> Organisat """ return await self._registry_client.fetch_item( id=id, - item_subtype=ItemSubType.ORGANISATION, + item_subtype=self.item_subtype, fetch_response_model=OrganisationFetchResponse, seed_allowed=seed_allowed ) @@ -80,14 +446,83 @@ async def update(self, id: str, domain_info: OrganisationDomainInfo, reason: Opt """ return await self._registry_client.update_item( id=id, - item_subtype=ItemSubType.ORGANISATION, + item_subtype=self.item_subtype, domain_info=domain_info, reason=reason, update_response_model=StatusResponse, ) + async def list_items(self, list_items_payload: GeneralListRequest) -> OrganisationListResponse: + """ + Lists all organisations within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + + Returns + ------- + OrganisationListResponse: The list response + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=OrganisationListResponse + ) + + async def seed_item(self) -> OrganisationSeedResponse: + """ + Seeds an organisation in the registry + + Returns + ------- + OrganisationSeedResponse: The seed response + """ + return await self._registry_client.seed_item( + item_subtype=self.item_subtype, + seed_model_response=OrganisationSeedResponse + ) + + async def create_item(self, create_item_request: OrganisationDomainInfo) -> OrganisationCreateResponse: + """ + Creates an organisation in the registry + + Parameters + ---------- + create_item_request : OrganisationDomainInfo + The create item request + + Returns + ------- + OrganisationCreateResponse: The create response + """ + return await self._registry_client.create_item( + create_item_request=create_item_request, + item_subtype=self.item_subtype, + create_response_model=OrganisationCreateResponse + ) + + async def validate_item(self, validate_request: OrganisationDomainInfo) -> StatusResponse: + """ + Validates an organisation in the registry + + Parameters + ---------- + validate_request : OrganisationDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + -class ModelClient(ModuleService): +class PersonClient(RegistryBaseClass): _registry_client: RegistryClient def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: @@ -102,38 +537,34 @@ def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryC A config object which contains information related to the Provena instance. """ - # Module service - self._auth = auth - self._config = config - - # Clients related to the registry scoped as private. - self._registry_client = registry_client + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.PERSON) - async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> ModelFetchResponse: + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> PersonFetchResponse: """ - Fetches a model from the registry + Fetches a person from the registry Args: - id (str): The model ID + id (str): The person ID seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. Returns: - ModelFetchResponse: The fetch response + PersonFetch: The fetch response """ return await self._registry_client.fetch_item( id=id, - item_subtype=ItemSubType.MODEL, - fetch_response_model=ModelFetchResponse, + item_subtype=self.item_subtype, + fetch_response_model=PersonFetchResponse, seed_allowed=seed_allowed ) - - async def update(self, id: str, domain_info: ModelDomainInfo, reason: Optional[str]) -> StatusResponse: + + async def update(self, id: str, domain_info: PersonDomainInfo, reason: Optional[str]) -> StatusResponse: """ - Updates a model in the registry + Updates a person in the registry Args: - id (str): The id of the model - domain_info (ModelDomainInfo): The new domain info + id (str): The id of the organisation + domain_info (OrganisationDomainInfo): The new domain info reason (Optional[str]): The reason if any Returns: @@ -141,42 +572,1071 @@ async def update(self, id: str, domain_info: ModelDomainInfo, reason: Optional[s """ return await self._registry_client.update_item( id=id, - item_subtype=ItemSubType.MODEL, + item_subtype=self.item_subtype, domain_info=domain_info, reason=reason, update_response_model=StatusResponse, ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> PersonListResponse: + """Lists all person(s) within registry based on filter + criteria. + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload contaning the filter/sort criteria + """ -class Registry(ModuleService): - # L2 clients used - _registry_client: RegistryClient + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=PersonListResponse + ) + + async def seed_item(self) -> PersonSeedResponse: + """ + Seeds a person in the registry - # Sub modules - organisation: OrganisationClient - model: ModelClient + Returns + ------- + PersonSeedResponse: The seed response + """ - def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + return await self._registry_client.seed_item( + item_subtype=self.item_subtype, + seed_model_response=PersonSeedResponse + ) + + async def create_item(self, create_item_request: PersonDomainInfo) -> PersonCreateResponse: """ + Creates a person in the registry + + Parameters + ---------- + create_item_request : OrganisationDomainInfo + The create item request + + Returns + ------- + PersonCreateResponse: The create response + """ + + return await self._registry_client.create_item( + create_item_request=create_item_request, + item_subtype=self.item_subtype, + create_response_model=PersonCreateResponse + ) + + async def validate_item(self, validate_request: PersonDomainInfo) -> StatusResponse: + """ + Validates a person in the registry + + Parameters + ---------- + validate_request : PersonDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + +class CreateActivityClient(RegistryBaseClass): + _registry_client: RegistryClient + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ Parameters ---------- auth : AuthManager - An abstract interface containing the user's requested auth flow - method. + An abstract interface containing the user's requested auth flow method. config : Config - A config object which contains information related to the Provena - instance. + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. """ - # Module service - self._auth = auth - self._config = config + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.CREATE) - # Clients related to the registry scoped as private. - self._registry_client = registry_client + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> CreateFetchResponse: + """ + Fetches a create activity item from the registry - # Sub modules - self.organisation = OrganisationClient( - auth=auth, config=config, registry_client=registry_client) - self.model = ModelClient( - auth=auth, config=config, registry_client=registry_client) + Args: + id (str): The create activity item ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + CreateFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=CreateFetchResponse, + seed_allowed=seed_allowed + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> CreateListResponse: + """ + Lists all create activity items within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + + Returns + ------- + CreateListResponse: The list response + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=CreateListResponse + ) + + async def validate_item(self, validate_request: CreateDomainInfo) -> StatusResponse: + """ + Validates a create-item activity in the registry + + Parameters + ---------- + validate_request : CreateDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + + +class VersionActivityClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow method. + config : Config + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.VERSION) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> VersionFetchResponse: + """ + Fetches a version activity item from the registry + + Args: + id (str): The version activity item ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + VersionFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=VersionFetchResponse, + seed_allowed=seed_allowed + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> VersionListResponse: + """ + Lists all version activity items within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + + Returns + ------- + VersionListResponse: The list response + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=VersionListResponse + ) + + async def validate_item(self, validate_request: VersionDomainInfo) -> StatusResponse: + """ + Validates a version activity in the registry + + Parameters + ---------- + validate_request : VersionDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + +class ModelRunActivityClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow method. + config : Config + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.MODEL_RUN) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> ModelRunFetchResponse: + """ + Fetches a model run activity item from the registry + + Args: + id (str): The model run activity item ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + ModelRunFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=ModelRunFetchResponse, + seed_allowed=seed_allowed + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> ModelRunListResponse: + """ + Lists all model run activity items within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + + Returns + ------- + ModelRunListResponse: The list response + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=ModelRunListResponse + ) + + async def validate_item(self, validate_request: ModelRunDomainInfo) -> StatusResponse: + """ + Validates a model run in the registry + + Parameters + ---------- + validate_request : ModelRunDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + + +class ModelClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow + method. + config : Config + A config object which contains information related to the Provena + instance. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.MODEL) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> ModelFetchResponse: + """ + Fetches a model from the registry + + Args: + id (str): The model ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + ModelFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=ModelFetchResponse, + seed_allowed=seed_allowed + ) + + async def update(self, id: str, domain_info: ModelDomainInfo, reason: Optional[str]) -> StatusResponse: + """ + Updates a model in the registry + + Args: + id (str): The id of the model + domain_info (ModelDomainInfo): The new domain info + reason (Optional[str]): The reason if any + + Returns: + StatusResponse: Status response + """ + return await self._registry_client.update_item( + id=id, + item_subtype=self.item_subtype, + domain_info=domain_info, + reason=reason, + update_response_model=StatusResponse, + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> ModelListResponse: + """Lists all model(s) within registry based on filter + criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload contaning the filter/sort criteria + """ + + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=ModelListResponse + ) + + async def seed_item(self) -> ModelSeedResponse: + """ + Seeds a model in the registry + + Returns + ------- + ModelSeedResponse: The seed response + """ + + return await self._registry_client.seed_item( + item_subtype=self.item_subtype, + seed_model_response=ModelSeedResponse + ) + + + async def create_item(self, create_item_request: ModelDomainInfo) -> ModelCreateResponse: + """ + Creates a model in the registry + + Parameters + ---------- + create_item_request : ModelDomainInfo + The create item request + + Returns + ------- + ModelCreateResponse: The create response + """ + + return await self._registry_client.create_item( + create_item_request=create_item_request, + item_subtype=self.item_subtype, + create_response_model=ModelCreateResponse + ) + + async def validate_item(self, validate_request: ModelDomainInfo) -> StatusResponse: + """ + Validates a model item in the registry + + Parameters + ---------- + validate_request : ModelDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + + async def version_item(self, version_request: VersionRequest) -> VersionResponse: + """ + Versions a model in the registry + + Parameters + ---------- + version_request : VersionRequest + The version request + + Returns + ------- + VersionResponse: The version response + """ + + return await self._registry_client.version( + version_request=version_request, + item_subtype=self.item_subtype + ) + +class ModelRunWorkFlowClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow + method. + config : Config + A config object which contains information related to the Provena + instance. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.WORKFLOW_TEMPLATE) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> ModelRunWorkflowTemplateFetchResponse: + """ + Fetches a model run workflow template from the registry + + Args: + id (str): The model run workflow template ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + ModelFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=ModelRunWorkflowTemplateFetchResponse, + seed_allowed=seed_allowed + ) + + async def update(self, id: str, domain_info: ModelRunWorkflowTemplateDomainInfo, reason: Optional[str]) -> StatusResponse: + """ + Updates a model in the registry + + Args: + id (str): The id of the model + domain_info (ModelDomainInfo): The new domain info + reason (Optional[str]): The reason if any + + Returns: + StatusResponse: Status response + """ + return await self._registry_client.update_item( + id=id, + item_subtype=self.item_subtype, + domain_info=domain_info, + reason=reason, + update_response_model=StatusResponse, + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> ModelRunWorkflowTemplateListResponse: + """Lists all model(s) within registry based on filter + criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload contaning the filter/sort criteria + """ + + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=ModelRunWorkflowTemplateListResponse + ) + + async def seed_item(self) -> ModelRunWorkflowTemplateSeedResponse: + """ + Seeds a model run workflow template in the registry + + Returns + ------- + ModelRunWorkflowTemplateSeedResponse: The seed response + """ + + return await self._registry_client.seed_item( + item_subtype=self.item_subtype, + seed_model_response=ModelRunWorkflowTemplateSeedResponse + ) + + + async def create_item(self, create_item_request: ModelRunWorkflowTemplateDomainInfo) -> ModelRunWorkflowTemplateCreateResponse: + """ + Creates a model run workflow template in the registry + + Parameters + ---------- + create_item_request : ModelRunWorkflowTemplateDomainInfo + The create item request + + Returns + ------- + ModelRunWorkflowTemplateCreateResponse: The create response + """ + + return await self._registry_client.create_item( + create_item_request=create_item_request, + item_subtype=self.item_subtype, + create_response_model=ModelRunWorkflowTemplateCreateResponse + ) + + async def validate_item(self, validate_request: ModelRunWorkflowTemplateDomainInfo) -> StatusResponse: + """ + Validates a model run workflow template item in the registry + + Parameters + ---------- + validate_request : ModelRunWorkflowTemplateDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + async def version_item(self, version_request: VersionRequest) -> VersionResponse: + """ + Versions a model run workflow template in the registry + + Parameters + ---------- + version_request : VersionRequest + The version request + + Returns + ------- + VersionResponse: The version response + """ + + return await self._registry_client.version( + version_request=version_request, + item_subtype=self.item_subtype + ) + + +class DatasetTemplateClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow + method. + config : Config + A config object which contains information related to the Provena + instance. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.DATASET_TEMPLATE) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> DatasetTemplateFetchResponse: + """ + Fetches a dataset template from the registry + + Args: + id (str): The dataset template ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + DatasetTemplateFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=DatasetTemplateFetchResponse, + seed_allowed=seed_allowed + ) + + async def update(self, id: str, domain_info: DatasetTemplateDomainInfo, reason: Optional[str]) -> StatusResponse: + """ + Updates a dataset template in the registry + + Args: + id (str): The id of the dataset template + domain_info (DatasetTemplateDomainInfo): The new domain info + reason (Optional[str]): The reason if any + + Returns: + StatusResponse: Status response + """ + return await self._registry_client.update_item( + id=id, + item_subtype=self.item_subtype, + domain_info=domain_info, + reason=reason, + update_response_model=StatusResponse, + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> DatasetTemplateListResponse: + """ + Lists all dataset templates within the registry based on filter + criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=DatasetTemplateListResponse + ) + + async def seed_item(self) -> DatasetTemplateSeedResponse: + """ + Seeds a dataset template in the registry + + Returns + ------- + DatasetTemplateSeedResponse: The seed response + """ + return await self._registry_client.seed_item( + item_subtype=self.item_subtype, + seed_model_response=DatasetTemplateSeedResponse + ) + + async def create_item(self, create_item_request: DatasetTemplateDomainInfo) -> DatasetTemplateCreateResponse: + """ + Creates a dataset template in the registry + + Parameters + ---------- + create_item_request : DatasetTemplateDomainInfo + The create item request + + Returns + ------- + DatasetTemplateCreateResponse: The create response + """ + return await self._registry_client.create_item( + create_item_request=create_item_request, + item_subtype=self.item_subtype, + create_response_model=DatasetTemplateCreateResponse + ) + + async def validate_item(self, validate_request: DatasetTemplateDomainInfo) -> StatusResponse: + """ + Validates a dataset template item in the registry + + Parameters + ---------- + validate_request : DatasetTemplateDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + async def version_item(self, version_request: VersionRequest) -> VersionResponse: + """ + Versions a dataset template in the registry + + Parameters + ---------- + version_request : VersionRequest + The version request + + Returns + ------- + VersionResponse: The version response + """ + return await self._registry_client.version( + version_request=version_request, + item_subtype=self.item_subtype + ) + + +class DatasetClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow method. + config : Config + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.DATASET) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> DatasetFetchResponse: + """ + Fetches a dataset from the registry + + Args: + id (str): The dataset ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + DatasetFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=DatasetFetchResponse, + seed_allowed=seed_allowed + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> DatasetListResponse: + """ + Lists all datasets within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + + Returns + ------- + DatasetListResponse: The list response + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=DatasetListResponse + ) + + + async def validate_item(self, validate_request: DatasetDomainInfo) -> StatusResponse: + """ + Validates a dataset item in the registry + + Parameters + ---------- + validate_request : DatasetDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + +class StudyClient(RegistryBaseClass): + _registry_client: RegistryClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow method. + config : Config + A config object which contains information related to the Provena instance. + registry_client : RegistryClient + The registry client to use for registry interactions. + """ + + super().__init__(auth=auth, config=config, registry_client=registry_client, item_subtype=ItemSubType.STUDY) + + async def fetch(self, id: str, seed_allowed: Optional[bool] = None) -> StudyFetchResponse: + """ + Fetches a study from the registry + + Args: + id (str): The study ID + seed_allowed (Optional[bool], optional): Allow seed items. Defaults to None. + + Returns: + StudyFetchResponse: The fetch response + """ + return await self._registry_client.fetch_item( + id=id, + item_subtype=self.item_subtype, + fetch_response_model=StudyFetchResponse, + seed_allowed=seed_allowed + ) + + async def list_items(self, list_items_payload: GeneralListRequest) -> StudyListResponse: + """ + Lists all studies within the registry based on filter criteria. + + Parameters + ---------- + list_items_payload : GeneralListRequest + Payload containing the filter/sort criteria + + Returns + ------- + StudyListResponse: The list response + """ + return await self._registry_client.list_items( + list_items_payload=list_items_payload, + item_subtype=self.item_subtype, + update_model_response=StudyListResponse + ) + + async def seed_item(self) -> StudySeedResponse: + """ + Seeds a study in the registry + + Returns + ------- + StudySeedResponse: The seed response + """ + return await self._registry_client.seed_item( + item_subtype=self.item_subtype, + seed_model_response=StudySeedResponse + ) + + async def update(self, id: str, domain_info: StudyDomainInfo, reason: Optional[str]) -> StatusResponse: + """ + Updates a study in the registry + + Parameters + ---------- + id : str + The id of the study + domain_info : StudyDomainInfo + The new domain info + reason : Optional[str] + The reason, if any + + Returns + ------- + StatusResponse: Status response + """ + return await self._registry_client.update_item( + id=id, + item_subtype=self.item_subtype, + domain_info=domain_info, + reason=reason, + update_response_model=StatusResponse, + ) + + async def create_item(self, create_item_request: StudyDomainInfo) -> StudyCreateResponse: + """ + Creates a study in the registry + + Parameters + ---------- + create_item_request : StudyDomainInfo + The create item request + + Returns + ------- + StudyCreateResponse: The create response + """ + return await self._registry_client.create_item( + create_item_request=create_item_request, + item_subtype=self.item_subtype, + create_response_model=StudyCreateResponse + ) + + async def validate_item(self, validate_request: StudyDomainInfo) -> StatusResponse: + """ + Validates a study item in the registry + + Parameters + ---------- + validate_request : StudyDomainInfo + The validate request + + Returns + ------- + StatusResponse: The status response + """ + + return await self._registry_client.validate_item( + validate_request=validate_request, + item_subtype=self.item_subtype + ) + + +class Registry(ModuleService): + # L2 clients used + _registry_client: RegistryClient + + # Admin sub module + admin: RegistryAdminClient + + # Sub modules + organisation: OrganisationClient + person: PersonClient + model: ModelClient + model_run_workflow: ModelRunWorkFlowClient + dataset_template: DatasetTemplateClient + dataset: DatasetClient + study: StudyClient + create_activity: CreateActivityClient + version_acitvity: VersionActivityClient + model_run: ModelRunActivityClient + + def __init__(self, auth: AuthManager, config: Config, registry_client: RegistryClient) -> None: + """ + + Parameters + ---------- + auth : AuthManager + An abstract interface containing the user's requested auth flow + method. + config : Config + A config object which contains information related to the Provena + instance. + """ + # Module service + self._auth = auth + self._config = config + + # Clients related to the registry scoped as private. + self._registry_client = registry_client + + # Admin sub module + self.admin = RegistryAdminClient( + auth=auth, config=config, registry_client=registry_client + ) + + # Sub modules + self.organisation = OrganisationClient( + auth=auth, config=config, registry_client=registry_client) + self.person = PersonClient( + auth=auth, config=config, registry_client=registry_client + ) + self.model = ModelClient( + auth=auth, config=config, registry_client=registry_client) + self.model_run_workflow = ModelRunWorkFlowClient( + auth=auth, config=config, registry_client=registry_client + ) + self.dataset_template = DatasetTemplateClient( + auth=auth, config=config, registry_client=registry_client + ) + self.study = StudyClient( + auth=auth, config=config, registry_client=registry_client + ) + self.create_activity = CreateActivityClient( + auth=auth, config=config, registry_client=registry_client) + self.version_acitvity = VersionActivityClient( + auth=auth, config=config, registry_client=registry_client) + self.model_run = ModelRunActivityClient( + auth=auth, config=config, registry_client=registry_client + ) + + async def get_health_check(self) -> HealthCheckResponse: + """ + Health check the API + + Returns: + HealthCheckResponse: Response + """ + return await self._registry_client.get_health_check() + + + async def list_general_registry_items(self, general_list_request: GeneralListRequest) -> PaginatedListResponse: + """ + Lists general registry items based on filter criteria. + + Parameters + ---------- + general_list_request : GeneralListRequest + The request containing filter and sort criteria. + + Returns + ------- + PaginatedListResponse + The response containing the paginated list of registry items. + """ + + return await self._registry_client.general.list_general_registry_items( + general_list_request=general_list_request + ) + + + async def general_fetch_item(self, id: str) -> UntypedFetchResponse: + """ + Fetches a general item from the registry. + + Parameters + ---------- + id : str + The ID of the item to fetch. + + Returns + ------- + UntypedFetchResponse + The fetch response containing the item details. + """ + + return await self._registry_client.general.general_fetch_item( + id=id + ) + + + async def get_current_provena_version(self) -> VersionResponse: + """ + Gets the current Provena version deployed on your domain. + + Returns + ------- + VersionResponse + The response containing the current Provena version. + """ + + return await self._registry_client.general.get_current_provena_version() diff --git a/src/provenaclient/utils/helpers.py b/src/provenaclient/utils/helpers.py index 0edf482..ef6bee2 100644 --- a/src/provenaclient/utils/helpers.py +++ b/src/provenaclient/utils/helpers.py @@ -19,7 +19,7 @@ from provenaclient.utils.exceptions import AuthException, HTTPValidationException, ServerException, BadRequestException, ValidationException, NotFoundException from provenaclient.utils.exceptions import BaseException from ProvenaInterfaces.SharedTypes import StatusResponse -from ProvenaInterfaces.RegistryModels import ItemBase +from ProvenaInterfaces.RegistryModels import ItemBase, ItemSubType import os # Type var to refer to base models @@ -34,6 +34,35 @@ ParamTypes = Union[str, int, bool] +def convert_to_item_subtype(item_subtype_str: Optional[str]) -> ItemSubType: + """Converts a string into ItemSubType supported enum type. + + Parameters + ---------- + item_subtype_str : Optional[str] + Optional string containing similar enum text. + + Returns + ------- + ItemSubType + Enum type of ItemSubType. + + Raises + ------ + ValueError + Item subtype field was not present. + ValueError + Item subtype cannot be converted to ENUM. + """ + + if item_subtype_str is None: + raise ValueError("Item subtype field not found!") + + try: + return ItemSubType[item_subtype_str.upper()] + except KeyError as e: + raise ValueError(f"Invalid item_subtype: {item_subtype_str}") from e + def get_and_validate_file_path(file_path: Optional[str], write_to_file: bool, default_file_name: str) -> Optional[str]: """Determine and validate the file path for writing a file. diff --git a/src/provenaclient/utils/registry_endpoints.py b/src/provenaclient/utils/registry_endpoints.py index d16b5af..c4d0aaa 100644 --- a/src/provenaclient/utils/registry_endpoints.py +++ b/src/provenaclient/utils/registry_endpoints.py @@ -33,6 +33,7 @@ class RegistryAction(str, Enum): UNLOCK = "UNLOCK" REVERT = "REVERT" LOCK_HISTORY = "LOCK_HISTORY" + DELETE = "DELETE" # Define the mappings action_postfixes: Dict[RegistryAction, str] = { @@ -52,6 +53,7 @@ class RegistryAction(str, Enum): RegistryAction.LOCK: "/locks/lock", RegistryAction.UNLOCK: "/locks/unlock", RegistryAction.LOCK_HISTORY: "/locks/history", + RegistryAction.DELETE: "/delete" } subtype_route_prefixes: Dict[ItemSubType, str] = { diff --git a/tests/adhoc.py b/tests/adhoc.py index cf05462..351b098 100644 --- a/tests/adhoc.py +++ b/tests/adhoc.py @@ -2,9 +2,9 @@ from provenaclient import ProvenaClient, Config from provenaclient.auth import DeviceFlow from ProvenaInterfaces.RegistryModels import * -from ProvenaInterfaces.RegistryAPI import NoFilterSubtypeListRequest, SortOptions, SortType -from ProvenaInterfaces.ProvenanceAPI import ModelRunRecord, TemplatedDataset, RegisterBatchModelRunRequest -from ProvenaInterfaces.ProvenanceModels import DatasetType, AssociationInfo +from ProvenaInterfaces.RegistryAPI import * +from ProvenaInterfaces.ProvenanceAPI import * +from ProvenaInterfaces.ProvenanceModels import * import asyncio from provenaclient.auth.manager import Log from typing import List @@ -180,6 +180,8 @@ async def main() -> None: # res = await client.prov_api.admin.store_multiple_records(registry_record=list_of_model_runs) + """ + last: str = "" async for ds in client.datastore.for_all_datasets( list_dataset_request=NoFilterSubtypeListRequest( @@ -216,4 +218,34 @@ def random_num() -> int: return random.randint(100, 1000) print_list=True ) + """ + + general_list_request = GeneralListRequest( + filter_by=None, + sort_by=None, + pagination_key=None + ) + + response = await client.registry.model.list_items(list_items_payload=general_list_request) + print(response) + + + #res = await client.registry.admin.delete(id="10378.1/1913346") + + #print(res) + + #print(await client.registry.model.get_auth_configuration(id = "10378.1/1875946")) # Existing Model in DEV + + model_create_request = ModelDomainInfo( + display_name="Parth testing", + user_metadata=None, + name= "Parth adchoc model", + description="Parth testing adhoc model", + documentation_url= "https://example.com.au", #type:ignore + source_url= "https://example.com.au" #type:ignore + ) + + # res_two = await client.registry.model.create_item(create_item_request=model_create_request) - Disabled to avoid spamming create models. + #print(res_two) + asyncio.run(main())