diff --git a/addon_service/addon_imp/instantiation.py b/addon_service/addon_imp/instantiation.py index 83427498..e6f48dbf 100644 --- a/addon_service/addon_imp/instantiation.py +++ b/addon_service/addon_imp/instantiation.py @@ -11,6 +11,10 @@ CitationAddonImp, CitationConfig, ) +from addon_toolkit.interfaces.remote_computing import ( + RemoteComputingAddonImp, + RemoteComputingConfig, +) from addon_toolkit.interfaces.storage import ( StorageAddonClientRequestorImp, StorageAddonHttpRequestorImp, @@ -23,6 +27,7 @@ from addon_service.authorized_account.models import AuthorizedAccount from addon_service.models import ( AuthorizedCitationAccount, + AuthorizedRemoteComputingAccount, AuthorizedStorageAccount, ) @@ -30,12 +35,14 @@ async def get_addon_instance( imp_cls: type[AddonImp], account: AuthorizedAccount, - config: StorageConfig | CitationConfig, + config: StorageConfig | CitationConfig | RemoteComputingConfig, ) -> AddonImp: if issubclass(imp_cls, StorageAddonImp): return await get_storage_addon_instance(imp_cls, account, config) elif issubclass(imp_cls, CitationAddonImp): return await get_citation_addon_instance(imp_cls, account, config) + elif issubclass(imp_cls, RemoteComputingAddonImp): + return await get_remote_computing_addon_instance(imp_cls, account, config) raise ValueError(f"unknown addon type {imp_cls}") @@ -96,3 +103,26 @@ async def get_citation_addon_instance( get_citation_addon_instance__blocking = async_to_sync(get_citation_addon_instance) + + +async def get_remote_computing_addon_instance( + imp_cls: type[RemoteComputingAddonImp], + account: AuthorizedRemoteComputingAccount, + config: RemoteComputingConfig, +) -> RemoteComputingAddonImp: + """create an instance of a `RemoteComputingAddonImp`""" + + assert issubclass(imp_cls, RemoteComputingAddonImp) + return imp_cls( + config=config, + network=GravyvaletHttpRequestor( + client_session=await get_singleton_client_session(), + prefix_url=config.external_api_url, + account=account, + ), + ) + + +get_remote_computing_addon_instance__blocking = async_to_sync( + get_remote_computing_addon_instance +) diff --git a/addon_service/admin/__init__.py b/addon_service/admin/__init__.py index 847cebdf..ee9debe8 100644 --- a/addon_service/admin/__init__.py +++ b/addon_service/admin/__init__.py @@ -49,6 +49,22 @@ class ExternalCitationServiceAdmin(GravyvaletModelAdmin): } +@admin.register(models.ExternalRemoteComputingService) +class ExternalRemoteComputingServiceAdmin(GravyvaletModelAdmin): + list_display = ("display_name", "created", "modified") + readonly_fields = ( + "id", + "created", + "modified", + ) + raw_id_fields = ("oauth2_client_config",) + enum_choice_fields = { + "int_addon_imp": known_imps.AddonImpNumbers, + "int_credentials_format": CredentialsFormats, + "int_service_type": ServiceTypes, + } + + @admin.register(models.OAuth2ClientConfig) @linked_many_field("external_storage_services") @linked_many_field("external_citation_services") diff --git a/addon_service/common/serializer_fields.py b/addon_service/common/serializer_fields.py index 3af7bc57..a1039445 100644 --- a/addon_service/common/serializer_fields.py +++ b/addon_service/common/serializer_fields.py @@ -56,10 +56,14 @@ def to_representation(self, value): data = super().to_representation(value) if hasattr(value, "authorizedcitationaccount"): data["type"] = "authorized-citation-accounts" + elif hasattr(value, "authorizedremotecomputingaccount"): + data["type"] = "authorized-remotecomputing-accounts" elif hasattr(value, "authorizedstorageaccount"): data["type"] = "authorized-storage-accounts" elif hasattr(value, "configuredcitationaddon"): data["type"] = "configured-citation-addons" + elif hasattr(value, "configuredremotecomputingaddon"): + data["type"] = "configured-remotecomputing-addons" elif hasattr(value, "configuredstorageaccount"): data["type"] = "configured-storage-addons" return data diff --git a/addon_service/common/validators.py b/addon_service/common/validators.py index c7a55877..124b5dc0 100644 --- a/addon_service/common/validators.py +++ b/addon_service/common/validators.py @@ -9,6 +9,7 @@ from addon_toolkit import AddonCapabilities from addon_toolkit.interfaces.citation import CitationAddonImp +from addon_toolkit.interfaces.remote_computing import RemoteComputingAddonImp from addon_toolkit.interfaces.storage import StorageAddonImp from . import known_imps @@ -62,6 +63,10 @@ def validate_citation_imp_number(value): _validate_imp_number(value, CitationAddonImp) +def validate_remote_computing_imp_number(value): + _validate_imp_number(value, RemoteComputingAddonImp) + + ### # module-private helpers diff --git a/addon_service/credentials/models.py b/addon_service/credentials/models.py index 3a7c88cb..2071ada4 100644 --- a/addon_service/credentials/models.py +++ b/addon_service/credentials/models.py @@ -18,6 +18,7 @@ class ExternalCredentials(AddonsServiceBaseModel): # Attributes inherited from back-references: # storage (AuthorizedStorageAccount._credentials, One2One) + # FIXME: Where is citation and remote_computing? class Meta: verbose_name = "External Credentials" @@ -104,6 +105,9 @@ def authorized_accounts(self): ) ] except ExternalCredentials.authorized_storage_account.RelatedObjectDoesNotExist: + # FIXME: I have doubts that auth_storage_acct is correct. Shouldn't it be storage? + # FIXME: Where is citation and remote_computing? + # FIXME: does that docstring need to be updated? return None @property diff --git a/addon_service/models.py b/addon_service/models.py index 3553b95b..a36fc487 100644 --- a/addon_service/models.py +++ b/addon_service/models.py @@ -4,11 +4,20 @@ from addon_service.addon_operation.models import AddonOperationModel from addon_service.addon_operation_invocation.models import AddonOperationInvocation from addon_service.authorized_account.citation.models import AuthorizedCitationAccount +from addon_service.authorized_account.remote_computing.models import ( + AuthorizedRemoteComputingAccount, +) from addon_service.authorized_account.storage.models import AuthorizedStorageAccount from addon_service.configured_addon.citation.models import ConfiguredCitationAddon +from addon_service.configured_addon.remote_computing.models import ( + ConfiguredRemoteComputingAddon, +) from addon_service.configured_addon.storage.models import ConfiguredStorageAddon from addon_service.credentials.models import ExternalCredentials from addon_service.external_service.citation.models import ExternalCitationService +from addon_service.external_service.remote_computing.models import ( + ExternalRemoteComputingService, +) from addon_service.external_service.storage.models import ExternalStorageService from addon_service.oauth1.models import OAuth1ClientConfig from addon_service.oauth2.models import ( @@ -35,4 +44,7 @@ "AuthorizedCitationAccount", "ConfiguredCitationAddon", "ExternalCitationService", + "AuthorizedRemoteComputingAccount", + "ConfiguredRemoteComputingAddon", + "ExternalRemoteComputingService", ) diff --git a/addon_service/oauth1/views.py b/addon_service/oauth1/views.py index ff3ffcef..ed7ef761 100644 --- a/addon_service/oauth1/views.py +++ b/addon_service/oauth1/views.py @@ -4,6 +4,9 @@ from django.http import HttpResponse from addon_service.authorized_account.citation.models import AuthorizedCitationAccount +from addon_service.authorized_account.remote_computing.models import ( + AuthorizedRemoteComputingAccount, +) from addon_service.authorized_account.storage.models import AuthorizedStorageAccount from addon_service.oauth1.utils import get_access_token from addon_service.osf_models.fields import decrypt_string @@ -20,6 +23,8 @@ def oauth1_callback_view(request): account = AuthorizedStorageAccount.objects.get(pk=pk) case "AuthorizedCitationAccount": account = AuthorizedCitationAccount.objects.get(pk=pk) + case "AuthorizedRemoteComputingAccount": + account = AuthorizedRemoteComputingAccount.objects.get(pk=pk) oauth1_client_config = account.external_service.oauth1_client_config final_credentials, other_info = async_to_sync(get_access_token)( diff --git a/addon_service/serializers.py b/addon_service/serializers.py index 41f2165e..cc2be8ce 100644 --- a/addon_service/serializers.py +++ b/addon_service/serializers.py @@ -11,6 +11,9 @@ from addon_service.authorized_account.polymorphic_serializers import ( AuthorizedAccountPolymorphicSerializer, ) +from addon_service.authorized_account.remote_computing.serializers import ( + AuthorizedRemoteComputingAccountSerializer, +) from addon_service.authorized_account.serializers import AuthorizedAccountSerializer from addon_service.authorized_account.storage.serializers import ( AuthorizedStorageAccountSerializer, @@ -21,6 +24,9 @@ from addon_service.configured_addon.polymorphic_serializers import ( ConfiguredAddonPolymorphicSerializer, ) +from addon_service.configured_addon.remote_computing.serializers import ( + ConfiguredRemoteComputingAddonSerializer, +) from addon_service.configured_addon.serializers import ConfiguredAddonSerializer from addon_service.configured_addon.storage.serializers import ( ConfiguredStorageAddonSerializer, @@ -28,6 +34,9 @@ from addon_service.external_service.citation.serializers import ( ExternalCitationServiceSerializer, ) +from addon_service.external_service.remote_computing.serializers import ( + ExternalRemoteComputingServiceSerializer, +) from addon_service.external_service.serializers import ExternalServiceSerializer from addon_service.external_service.storage.serializers import ( ExternalStorageServiceSerializer, @@ -45,6 +54,9 @@ "ConfiguredCitationAddonSerializer", "ExternalCitationServiceSerializer", "AuthorizedCitationAccountSerializer", + "ConfiguredRemoteComputingAddonSerializer", + "ExternalRemoteComputingServiceSerializer", + "AuthorizedRemoteComputingAccountSerializer", "ResourceReferenceSerializer", "AddonImpSerializer", "AddonOperationInvocationSerializer", diff --git a/addon_service/urls.py b/addon_service/urls.py index 62df6370..6af91f71 100644 --- a/addon_service/urls.py +++ b/addon_service/urls.py @@ -57,6 +57,9 @@ def _register_viewset(viewset): _register_viewset(views.AuthorizedCitationAccountViewSet) _register_viewset(views.ConfiguredCitationAddonViewSet) _register_viewset(views.ExternalCitationServiceViewSet) +_register_viewset(views.AuthorizedRemoteComputingAccountViewSet) +_register_viewset(views.ConfiguredRemoteComputingAddonViewSet) +_register_viewset(views.ExternalRemoteComputingServiceViewSet) _register_viewset(views.ResourceReferenceViewSet) _register_viewset(views.AddonOperationInvocationViewSet) _register_viewset(views.AddonOperationViewSet) diff --git a/addon_service/views.py b/addon_service/views.py index 86e25731..288e4775 100644 --- a/addon_service/views.py +++ b/addon_service/views.py @@ -13,12 +13,21 @@ from addon_service.authorized_account.citation.views import ( AuthorizedCitationAccountViewSet, ) +from addon_service.authorized_account.remote_computing.views import ( + AuthorizedRemoteComputingAccountViewSet, +) from addon_service.authorized_account.storage.views import ( AuthorizedStorageAccountViewSet, ) from addon_service.configured_addon.citation.views import ConfiguredCitationAddonViewSet +from addon_service.configured_addon.remote_computing.views import ( + ConfiguredRemoteComputingAddonViewSet, +) from addon_service.configured_addon.storage.views import ConfiguredStorageAddonViewSet from addon_service.external_service.citation.views import ExternalCitationServiceViewSet +from addon_service.external_service.remote_computing.views import ( + ExternalRemoteComputingServiceViewSet, +) from addon_service.external_service.storage.views import ExternalStorageServiceViewSet from addon_service.oauth1.views import oauth1_callback_view from addon_service.oauth2.views import oauth2_callback_view @@ -49,6 +58,9 @@ async def status(request): "AuthorizedCitationAccountViewSet", "ConfiguredCitationAddonViewSet", "ExternalCitationServiceViewSet", + "AuthorizedRemoteComputingAccountViewSet", + "ConfiguredRemoteComputingAddonViewSet", + "ExternalRemoteComputingServiceViewSet", "AuthorizedStorageAccountViewSet", "ConfiguredStorageAddonViewSet", "ExternalStorageServiceViewSet",