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)
 
 

Reply via email to