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 77396b168 refactor(portal): make application-deployment serializers 
proto-native (Track D) (#201)
77396b168 is described below

commit 77396b168d75209929acb7af9ae4dca95ff8e6f0
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 04:15:48 2026 -0400

    refactor(portal): make application-deployment serializers proto-native 
(Track D) (#201)
    
    Rewrite ApplicationDeploymentDescriptionSerializer + the nested 
CommandObject /
    SetEnvPaths serializers to read the gRPC ApplicationDeploymentDescription
    protobuf directly, emitting the same Thrift-named JSON keys.
    
    - parallelism renders as the Thrift int via the prefix-aligned parallelism 
field
      helper; the OrderedListField command/env-path lists still sort by
      commandOrder/envPathOrder; the queue-default cluster (defaultQueueName/
      NodeCount/CPUCount/Walltime) maps proto-0/'' -> null as before.
    - Repoint ApplicationDeploymentViewSet (list/instance/create/update/destroy/
      queues) + ApplicationModuleViewSet.application_deployments to pass 
protobuf
      through directly (the queues action now mutates proto batch-queue fields),
      dropping 
grpc_adapters.{application_deployment,_command_object,_set_env_paths}
      + grpc_requests equivalents and the Thrift
      ApplicationDeploymentDescription/CommandObject/SetEnvPaths/ParallelismType
      imports.
    
    Validated byte-for-byte (deployment full incl. sorted command/env lists +
    parallelism int + queue defaults, and minimal) vs the old adapter+serializer
    path; write path (incl. nested lists + parallelism reverse-bridge) produces 
an
    equivalent proto. manage.py check green; api test failures unchanged.
---
 .../django_airavata/apps/api/grpc_adapters.py      |  43 -----
 .../django_airavata/apps/api/grpc_requests.py      |  44 -----
 .../django_airavata/apps/api/serializers.py        | 185 ++++++++++++++++-----
 .../django_airavata/apps/api/views.py              |  39 +++--
 4 files changed, 161 insertions(+), 150 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 e2780b9ba..c5a01bff1 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -19,9 +19,6 @@ from airavata.model.appcatalog.computeresource.ttypes import (
 from airavata.model.appcatalog.groupresourceprofile.ttypes import (
     ResourceType as _ThriftResourceType,
 )
-from airavata.model.appcatalog.parallelism.ttypes import (
-    ApplicationParallelismType as _ThriftParallelismType,
-)
 from airavata.model.application.io.ttypes import DataType as _ThriftDataType
 from airavata.model.data.replica.ttypes import (
     DataProductType as _ThriftDataProductType,
@@ -169,46 +166,6 @@ def application_interface(pb):
     )
 
 
-def _command_object(pb):
-    """gRPC ``CommandObject`` -> ``CommandObjectSerializer`` shape."""
-    return SimpleNamespace(command=pb.command, commandOrder=pb.command_order)
-
-
-def _set_env_paths(pb):
-    """gRPC ``SetEnvPaths`` -> ``SetEnvPathsSerializer`` shape."""
-    return SimpleNamespace(
-        name=pb.name, value=pb.value, envPathOrder=pb.env_path_order)
-
-
-def application_deployment(pb):
-    """gRPC ``ApplicationDeploymentDescription`` -> 
``ApplicationDeploymentDescriptionSerializer`` shape.
-
-    Recursively adapts the nested command/env-path lists. ``parallelism`` is an
-    enum with the proto/Thrift integer mismatch (proto SERIAL=1 vs Thrift
-    SERIAL=0) -> bridged by name. The queue-default cluster maps the proto-zero
-    "unset" sentinels back to None so the serializer renders null as Thrift 
did.
-    """
-    return SimpleNamespace(
-        appDeploymentId=pb.app_deployment_id,
-        appModuleId=pb.app_module_id,
-        computeHostId=pb.compute_host_id,
-        executablePath=pb.executable_path,
-        parallelism=_thrift_enum(pb, 'parallelism', _ThriftParallelismType),
-        appDeploymentDescription=pb.app_deployment_description,
-        moduleLoadCmds=[_command_object(c) for c in pb.module_load_cmds],
-        libPrependPaths=[_set_env_paths(p) for p in pb.lib_prepend_paths],
-        libAppendPaths=[_set_env_paths(p) for p in pb.lib_append_paths],
-        setEnvironment=[_set_env_paths(p) for p in pb.set_environment],
-        preJobCommands=[_command_object(c) for c in pb.pre_job_commands],
-        postJobCommands=[_command_object(c) for c in pb.post_job_commands],
-        defaultQueueName=pb.default_queue_name or None,
-        defaultNodeCount=pb.default_node_count or None,
-        defaultCPUCount=pb.default_cpu_count or None,
-        defaultWalltime=pb.default_walltime or None,
-        editableByUser=pb.editable_by_user,
-    )
-
-
 # proto ResourceType member name -> Thrift ResourceType value (names align,
 # ints differ: proto SLURM=1 vs Thrift SLURM=0).
 _RESOURCE_TYPE = {
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_requests.py 
b/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
index 5af94b865..7cb80e006 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_requests.py
@@ -17,9 +17,6 @@ import importlib
 from airavata.model.appcatalog.computeresource.ttypes import (
     JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
 )
-from airavata.model.appcatalog.parallelism.ttypes import (
-    ApplicationParallelismType as _ThriftParallelismType,
-)
 from airavata.model.appcatalog.groupresourceprofile.ttypes import (
     ResourceType as _ThriftResourceType,
 )
@@ -125,47 +122,6 @@ def application_interface(t):
     )
 
 
-def _command_object(t):
-    return _pb2("appcatalog.appdeployment.app_deployment_pb2").CommandObject(
-        command=t.command or '',
-        command_order=t.commandOrder or 0,
-    )
-
-
-def _set_env_paths(t):
-    return _pb2("appcatalog.appdeployment.app_deployment_pb2").SetEnvPaths(
-        name=t.name or '',
-        value=t.value or '',
-        env_path_order=t.envPathOrder or 0,
-    )
-
-
-def application_deployment(t):
-    """Thrift ``ApplicationDeploymentDescription`` -> proto message."""
-    dep = _pb2("appcatalog.appdeployment.app_deployment_pb2")
-    return dep.ApplicationDeploymentDescription(
-        app_deployment_id=t.appDeploymentId or '',
-        app_module_id=t.appModuleId or '',
-        compute_host_id=t.computeHostId or '',
-        executable_path=t.executablePath or '',
-        parallelism=_proto_enum(
-            _pb2("parallelism.parallelism_pb2").ApplicationParallelismType,
-            _ThriftParallelismType, t.parallelism),
-        app_deployment_description=t.appDeploymentDescription or '',
-        module_load_cmds=[_command_object(c) for c in (t.moduleLoadCmds or 
[])],
-        lib_prepend_paths=[_set_env_paths(p) for p in (t.libPrependPaths or 
[])],
-        lib_append_paths=[_set_env_paths(p) for p in (t.libAppendPaths or [])],
-        set_environment=[_set_env_paths(p) for p in (t.setEnvironment or [])],
-        pre_job_commands=[_command_object(c) for c in (t.preJobCommands or 
[])],
-        post_job_commands=[_command_object(c) for c in (t.postJobCommands or 
[])],
-        default_queue_name=t.defaultQueueName or '',
-        default_node_count=t.defaultNodeCount or 0,
-        default_cpu_count=t.defaultCPUCount or 0,
-        default_walltime=t.defaultWalltime or 0,
-        editable_by_user=bool(t.editableByUser),
-    )
-
-
 # proto-name -> Thrift protocol value maps (mirror grpc_adapters); inverted
 # below to go Thrift value -> proto value, preserving the divergent name pairs
 # (Thrift CLOUD <-> proto JSP_CLOUD; Thrift LOCAL <-> proto
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index ab1e225c3..d65db1301 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -7,11 +7,6 @@ from pathlib import Path
 from urllib.parse import quote
 from airavata.model.application.io.ttypes import DataType
 
-from airavata.model.appcatalog.appdeployment.ttypes import (
-    ApplicationDeploymentDescription,
-    CommandObject,
-    SetEnvPaths
-)
 from airavata.model.appcatalog.appinterface.ttypes import (
     ApplicationInterfaceDescription
 )
@@ -761,58 +756,162 @@ class 
ApplicationInterfaceDescriptionSerializer(serializers.Serializer):
         return request.is_gateway_admin
 
 
-class CommandObjectSerializer(
-        thrift_utils.create_serializer_class(CommandObject)):
-    pass
+def _app_deployment_pb2():
+    from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.appdeployment 
import (  # noqa: E501
+        app_deployment_pb2,
+    )
+    return app_deployment_pb2
+
 
+def _parallelism_field(**kwargs):
+    from airavata.model.appcatalog.parallelism.ttypes import (
+        ApplicationParallelismType as _ThriftParallelismType,
+    )
+    from airavata_sdk.generated.org.apache.airavata.model.parallelism import (
+        parallelism_pb2,
+    )
+    return proto_enum_int_field(
+        parallelism_pb2.ApplicationParallelismType.DESCRIPTOR,
+        _ThriftParallelismType, proto_prefix='APPLICATION_PARALLELISM_TYPE_',
+        **kwargs)
 
-class SetEnvPathsSerializer(
-        thrift_utils.create_serializer_class(SetEnvPaths)):
-    pass
 
+class CommandObjectSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``CommandObject`` message."""
 
-class ApplicationDeploymentDescriptionSerializer(
-    thrift_utils.create_serializer_class(
-        ApplicationDeploymentDescription)):
+    command = serializers.CharField(
+        allow_blank=True, allow_null=True, required=False)
+    commandOrder = serializers.IntegerField(
+        source='command_order', allow_null=True, required=False)
+
+    def create(self, validated_data):
+        return _app_deployment_pb2().CommandObject(
+            command=validated_data.get('command', '') or '',
+            command_order=validated_data.get('command_order', 0) or 0,
+        )
+
+
+class SetEnvPathsSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC ``SetEnvPaths`` message."""
+
+    name = serializers.CharField(
+        allow_blank=True, allow_null=True, required=False)
+    value = serializers.CharField(
+        allow_blank=True, allow_null=True, required=False)
+    envPathOrder = serializers.IntegerField(
+        source='env_path_order', allow_null=True, required=False)
+
+    def create(self, validated_data):
+        return _app_deployment_pb2().SetEnvPaths(
+            name=validated_data.get('name', '') or '',
+            value=validated_data.get('value', '') or '',
+            env_path_order=validated_data.get('env_path_order', 0) or 0,
+        )
+
+
+class ApplicationDeploymentDescriptionSerializer(serializers.Serializer):
+    """Proto-native serializer for the gRPC 
``ApplicationDeploymentDescription``."""
+
+    appDeploymentId = serializers.CharField(
+        source='app_deployment_id', allow_blank=True, allow_null=True,
+        required=False)
+    appModuleId = serializers.CharField(
+        source='app_module_id', allow_blank=True, allow_null=True,
+        required=False)
+    computeHostId = serializers.CharField(
+        source='compute_host_id', allow_blank=True, allow_null=True,
+        required=False)
+    executablePath = serializers.CharField(
+        source='executable_path', allow_blank=True, allow_null=True,
+        required=False)
+    parallelism = _parallelism_field(required=False, allow_null=True)
+    appDeploymentDescription = serializers.CharField(
+        source='app_deployment_description', allow_blank=True, allow_null=True,
+        required=False)
+    moduleLoadCmds = OrderedListField(
+        source='module_load_cmds', order_by='commandOrder',
+        child=CommandObjectSerializer(), allow_null=True, required=False)
+    libPrependPaths = OrderedListField(
+        source='lib_prepend_paths', order_by='envPathOrder',
+        child=SetEnvPathsSerializer(), allow_null=True, required=False)
+    libAppendPaths = OrderedListField(
+        source='lib_append_paths', order_by='envPathOrder',
+        child=SetEnvPathsSerializer(), allow_null=True, required=False)
+    setEnvironment = OrderedListField(
+        source='set_environment', order_by='envPathOrder',
+        child=SetEnvPathsSerializer(), allow_null=True, required=False)
+    preJobCommands = OrderedListField(
+        source='pre_job_commands', order_by='commandOrder',
+        child=CommandObjectSerializer(), allow_null=True, required=False)
+    postJobCommands = OrderedListField(
+        source='post_job_commands', order_by='commandOrder',
+        child=CommandObjectSerializer(), allow_null=True, required=False)
+    defaultQueueName = serializers.CharField(
+        source='default_queue_name', allow_blank=True, allow_null=True,
+        required=False)
+    defaultNodeCount = ProtoIntOrNoneField(source='default_node_count')
+    defaultCPUCount = ProtoIntOrNoneField(source='default_cpu_count')
+    defaultWalltime = ProtoIntOrNoneField(source='default_walltime')
+    editableByUser = serializers.BooleanField(
+        source='editable_by_user', required=False, default=False)
     url = FullyEncodedHyperlinkedIdentityField(
         view_name='django_airavata_api:application-deployment-detail',
-        lookup_field='appDeploymentId',
+        lookup_field='app_deployment_id',
         lookup_url_kwarg='app_deployment_id')
-    # Default values returned in these results have been overridden with app
-    # deployment defaults for any that exist
     queues = FullyEncodedHyperlinkedIdentityField(
         view_name='django_airavata_api:application-deployment-queues',
-        lookup_field='appDeploymentId',
+        lookup_field='app_deployment_id',
         lookup_url_kwarg='app_deployment_id')
     userHasWriteAccess = serializers.SerializerMethodField()
-    moduleLoadCmds = OrderedListField(
-        order_by='commandOrder',
-        child=CommandObjectSerializer(),
-        allow_null=True)
-    preJobCommands = OrderedListField(
-        order_by='commandOrder',
-        child=CommandObjectSerializer(),
-        allow_null=True)
-    postJobCommands = OrderedListField(
-        order_by='commandOrder',
-        child=CommandObjectSerializer(),
-        allow_null=True)
-    libPrependPaths = OrderedListField(
-        order_by='envPathOrder',
-        child=SetEnvPathsSerializer(),
-        allow_null=True)
-    libAppendPaths = OrderedListField(
-        order_by='envPathOrder',
-        child=SetEnvPathsSerializer(),
-        allow_null=True)
-    setEnvironment = OrderedListField(
-        order_by='envPathOrder',
-        child=SetEnvPathsSerializer(),
-        allow_null=True)
+
+    def to_representation(self, instance):
+        ret = super().to_representation(instance)
+        # The proto string default '' must render as null for the optional
+        # default-queue-name field (the old adapter mapped pb.<f> or None).
+        if ret.get('defaultQueueName') == '':
+            ret['defaultQueueName'] = None
+        return ret
 
     def get_userHasWriteAccess(self, appDeployment):
         return user_has_access(
-            self.context['request'], appDeployment.appDeploymentId)
+            self.context['request'], appDeployment.app_deployment_id)
+
+    def create(self, validated_data):
+        a = _app_deployment_pb2()
+        return a.ApplicationDeploymentDescription(
+            app_module_id=validated_data.get('app_module_id', '') or '',
+            compute_host_id=validated_data.get('compute_host_id', '') or '',
+            executable_path=validated_data.get('executable_path', '') or '',
+            parallelism=validated_data.get('parallelism', 0) or 0,
+            app_deployment_description=validated_data.get(
+                'app_deployment_description', '') or '',
+            module_load_cmds=[
+                CommandObjectSerializer().create(c)
+                for c in validated_data.get('module_load_cmds', []) or []],
+            lib_prepend_paths=[
+                SetEnvPathsSerializer().create(p)
+                for p in validated_data.get('lib_prepend_paths', []) or []],
+            lib_append_paths=[
+                SetEnvPathsSerializer().create(p)
+                for p in validated_data.get('lib_append_paths', []) or []],
+            set_environment=[
+                SetEnvPathsSerializer().create(p)
+                for p in validated_data.get('set_environment', []) or []],
+            pre_job_commands=[
+                CommandObjectSerializer().create(c)
+                for c in validated_data.get('pre_job_commands', []) or []],
+            post_job_commands=[
+                CommandObjectSerializer().create(c)
+                for c in validated_data.get('post_job_commands', []) or []],
+            default_queue_name=validated_data.get('default_queue_name', '') or 
'',
+            default_node_count=validated_data.get('default_node_count', 0) or 
0,
+            default_cpu_count=validated_data.get('default_cpu_count', 0) or 0,
+            default_walltime=validated_data.get('default_walltime', 0) or 0,
+            editable_by_user=bool(validated_data.get('editable_by_user', 
False)),
+        )
+
+    def update(self, instance, validated_data):
+        return self.create(validated_data)
 
 
 class BatchQueueSerializer(serializers.Serializer):
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index 4382fd180..327caad6d 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -522,12 +522,11 @@ class ApplicationModuleViewSet(APIBackedViewSet):
 
     @action(detail=True)
     def application_deployments(self, request, app_module_id):
-        all_deployments = [
-            grpc_adapters.application_deployment(d)
-            for d in self.request.airavata.research
-            .get_accessible_application_deployments(self.gateway_id)]
+        all_deployments = (
+            self.request.airavata.research
+            .get_accessible_application_deployments(self.gateway_id))
         app_deployments = [
-            dep for dep in all_deployments if dep.appModuleId == app_module_id]
+            dep for dep in all_deployments if dep.app_module_id == 
app_module_id]
         serializer = serializers.ApplicationDeploymentDescriptionSerializer(
             app_deployments, many=True, context={'request': request})
         return Response(serializer.data)
@@ -662,48 +661,48 @@ class ApplicationDeploymentViewSet(APIBackedViewSet):
         else:
             deployments = 
self.request.airavata.research.get_accessible_application_deployments(
                 self.gateway_id)
-        return [grpc_adapters.application_deployment(d) for d in deployments]
+        return list(deployments)
 
     def get_instance(self, lookup_value):
-        return grpc_adapters.application_deployment(
-            
self.request.airavata.research.get_application_deployment(lookup_value))
+        return self.request.airavata.research.get_application_deployment(
+            lookup_value)
 
     def perform_create(self, serializer):
         application_deployment = serializer.save()
         app_deployment_id = 
self.request.airavata.research.register_application_deployment(
-            self.gateway_id, 
grpc_requests.application_deployment(application_deployment))
-        application_deployment.appDeploymentId = app_deployment_id
+            self.gateway_id, application_deployment)
+        application_deployment.app_deployment_id = app_deployment_id
 
     def perform_update(self, serializer):
         application_deployment = serializer.save()
         self.request.airavata.research.update_application_deployment(
-            application_deployment.appDeploymentId,
-            grpc_requests.application_deployment(application_deployment))
+            application_deployment.app_deployment_id,
+            application_deployment)
 
     def perform_destroy(self, instance):
         self.request.airavata.research.delete_application_deployment(
-            instance.appDeploymentId)
+            instance.app_deployment_id)
 
     @action(detail=True)
     def queues(self, request, app_deployment_id):
         """Return queues for this deployment with defaults overridden by 
deployment defaults if they exist"""
-        app_deployment = grpc_adapters.application_deployment(
+        app_deployment = (
             self.request.airavata.research.get_application_deployment(
                 app_deployment_id))
         compute_resource = request.airavata.compute.get_compute_resource(
-            app_deployment.computeHostId)
+            app_deployment.compute_host_id)
         # Override defaults with app deployment default queue, if defined
         batch_queues = []
         for batch_queue in compute_resource.batch_queues:
-            if app_deployment.defaultQueueName:
-                if app_deployment.defaultQueueName == batch_queue.queue_name:
+            if app_deployment.default_queue_name:
+                if app_deployment.default_queue_name == batch_queue.queue_name:
                     batch_queue.is_default_queue = True
                     batch_queue.default_node_count = (
-                        app_deployment.defaultNodeCount or 0)
+                        app_deployment.default_node_count or 0)
                     batch_queue.default_cpu_count = (
-                        app_deployment.defaultCPUCount or 0)
+                        app_deployment.default_cpu_count or 0)
                     batch_queue.default_walltime = (
-                        app_deployment.defaultWalltime or 0)
+                        app_deployment.default_walltime or 0)
                 else:
                     batch_queue.is_default_queue = False
             batch_queues.append(batch_queue)

Reply via email to