This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
The following commit(s) were added to refs/heads/main by this push:
new 987bd73d5 feat(portal): repoint application interface reads to gRPC
(Track D, D2) (#165)
987bd73d5 is described below
commit 987bd73d56333ba39822ce0f14a65c28d8867272
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 19:22:48 2026 -0400
feat(portal): repoint application interface reads to gRPC (Track D, D2)
(#165)
Migrate ApplicationInterfaceViewSet reads (get_list, get_instance,
compute_resources action) from the Thrift client to the gRPC research
facade (get_all_application_interfaces / get_application_interface /
get_available_app_interface_compute_resources). Write actions
(register/update/delete) stay on Thrift pending D3.
ApplicationInterfaceDescription is the first nested read family: the new
application_interface adapter recursively adapts the repeated
applicationInputs (InputDataObjectType) and applicationOutputs
(OutputDataObjectType) sub-messages to the Thrift attribute names the
serializer reads. The DataType enum has the same proto/Thrift integer
mismatch as SummaryType (proto STRING=1 vs Thrift STRING=0), so the
nested adapters bridge it by NAME via the shared _thrift_enum helper so
the serializer's EnumChoiceField(DataType) labels it correctly. metaData
(a JSON string) maps empty -> None so StoredJSONField renders null as it
did on Thrift. userHasWriteAccess here is gateway-admin-based (not
sharing), so it is unchanged.
Verified: manage.py check clean; empty list 200 and a seeded interface
round-trips (list + detail) against the live backend; offline serializer
render with real nested protobuf confirms inputs/outputs adapt fully,
DataType labels by name, metaData parses, url + queueSettingsCalculatorId
match the prior contract. (The dev backend does not persist interface
inputs/outputs/module links on registration, so nested live data was
validated via the offline render against real protobuf.)
---
.../django_airavata/apps/api/grpc_adapters.py | 60 ++++++++++++++++++++++
.../django_airavata/apps/api/views.py | 22 ++++----
2 files changed, 73 insertions(+), 9 deletions(-)
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index df6135482..30761dadf 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -13,6 +13,7 @@ serializers are made protobuf-native.
from types import SimpleNamespace
+from airavata.model.application.io.ttypes import DataType as _ThriftDataType
from airavata.model.credential.store.ttypes import SummaryType as
_ThriftSummaryType
@@ -104,3 +105,62 @@ def credential_summary(pb):
token=pb.token,
description=pb.description,
)
+
+
+def _input_data_object(pb):
+ """gRPC ``InputDataObjectType`` -> ``InputDataObjectTypeSerializer``
shape."""
+ return SimpleNamespace(
+ name=pb.name,
+ value=pb.value,
+ # DataType proto/Thrift ints differ per name -> bridge by name; the
+ # serializer's EnumChoiceField(DataType) reads ``.name`` off the
member.
+ type=_thrift_enum(pb, 'type', _ThriftDataType),
+ applicationArgument=pb.application_argument,
+ standardInput=pb.standard_input,
+ userFriendlyDescription=pb.user_friendly_description,
+ # JSON string; empty -> None so StoredJSONField renders null like
Thrift.
+ metaData=pb.meta_data or None,
+ inputOrder=pb.input_order,
+ isRequired=pb.is_required,
+ requiredToAddedToCommandLine=pb.required_to_added_to_command_line,
+ dataStaged=pb.data_staged,
+ storageResourceId=pb.storage_resource_id,
+ isReadOnly=pb.is_read_only,
+ overrideFilename=pb.override_filename,
+ )
+
+
+def _output_data_object(pb):
+ """gRPC ``OutputDataObjectType`` -> ``OutputDataObjectTypeSerializer``
shape."""
+ return SimpleNamespace(
+ name=pb.name,
+ value=pb.value,
+ type=_thrift_enum(pb, 'type', _ThriftDataType),
+ applicationArgument=pb.application_argument,
+ isRequired=pb.is_required,
+ requiredToAddedToCommandLine=pb.required_to_added_to_command_line,
+ dataMovement=pb.data_movement,
+ location=pb.location,
+ searchQuery=pb.search_query,
+ outputStreaming=pb.output_streaming,
+ storageResourceId=pb.storage_resource_id,
+ metaData=pb.meta_data or None,
+ )
+
+
+def application_interface(pb):
+ """gRPC ``ApplicationInterfaceDescription`` ->
``ApplicationInterfaceDescriptionSerializer`` shape.
+
+ Recursively adapts the nested ``applicationInputs``/``applicationOutputs``
+ repeated messages.
+ """
+ return SimpleNamespace(
+ applicationInterfaceId=pb.application_interface_id,
+ applicationName=pb.application_name,
+ applicationDescription=pb.application_description,
+ applicationModules=list(pb.application_modules),
+ applicationInputs=[_input_data_object(i) for i in
pb.application_inputs],
+ applicationOutputs=[_output_data_object(o) for o in
pb.application_outputs],
+ archiveWorkingDirectory=pb.archive_working_directory,
+ hasOptionalFileInputs=pb.has_optional_file_inputs,
+ )
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index ddb6e7fcd..078c9d4f8 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -554,18 +554,22 @@ class ApplicationInterfaceViewSet(APIBackedViewSet):
lookup_field = 'app_interface_id'
def get_list(self):
- return self.request.airavata_client.getAllApplicationInterfaces(
- self.authz_token, self.gateway_id)
+ return [
+ grpc_adapters.application_interface(i)
+ for i in
self.request.airavata.research.get_all_application_interfaces(
+ self.gateway_id)
+ ]
def get_instance(self, lookup_value):
try:
- return self.request.airavata_client.getApplicationInterface(
- self.authz_token, lookup_value)
+ return grpc_adapters.application_interface(
+ self.request.airavata.research.get_application_interface(
+ lookup_value))
except Exception:
# If it failed to load, check to see if it exists at all
- all_interfaces =
self.request.airavata_client.getAllApplicationInterfaces(
- self.authz_token, self.gateway_id)
- interface_ids = map(lambda i: i.applicationInterfaceId,
all_interfaces)
+ all_interfaces =
self.request.airavata.research.get_all_application_interfaces(
+ self.gateway_id)
+ interface_ids = [i.application_interface_id for i in
all_interfaces]
if lookup_value not in interface_ids:
raise Http404("Application interface does not exist")
else:
@@ -608,8 +612,8 @@ class ApplicationInterfaceViewSet(APIBackedViewSet):
@action(detail=True)
def compute_resources(self, request, app_interface_id):
- compute_resources =
request.airavata_client.getAvailableAppInterfaceComputeResources(
- self.authz_token, app_interface_id)
+ compute_resources =
request.airavata.research.get_available_app_interface_compute_resources(
+ app_interface_id)
return Response(compute_resources)