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 ca1632aa5 refactor(portal): drop Thrift model types from the 
serializer + view layer (Track D) (#206)
ca1632aa5 is described below

commit ca1632aa59518af13080154aaee13bbc0f14071b
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 05:27:06 2026 -0400

    refactor(portal): drop Thrift model types from the serializer + view layer 
(Track D) (#206)
    
    With every serializer proto-native, remove the remaining Thrift model-type
    references from apps/api/serializers.py, apps/api/views.py, 
context_processors.py,
    and apps/workspace/views.py — the contract still emits the historical 
Thrift enum
    INTEGER values, but those are now supplied by a hardcoded table rather than 
the
    legacy Thrift types.
    
    - serializers.py: new _THRIFT_ENUM_INTS table (member name -> Thrift int 
for all
      22 enums + FileSystems); the proto_enum_int_field / 
proto_enum_keyed_map_field
      factories and every enum field helper read it instead of importing the 
Thrift
      enum. ResourcePermissionType becomes a local IntEnum (used only as 
comparable
      sentinels; the sharing facade is addressed by member name). The file no 
longer
      imports airavata.model.* at all.
    - views.py: ExperimentSearchFields filter-key validation now reads the proto
      enum's member names; ResourcePermissionType references the local 
serializers
      enum.
    - context_processors.py: the notification-priority proto->Thrift-int bridge 
uses
      the hardcoded {LOW:0,NORMAL:1,HIGH:2} table.
    - workspace/views.py: the create-experiment input-type checks keep the 
historical
      Thrift DataType integers inline (preserving the pre-existing always-false
      string-vs-int comparison byte-for-byte).
    
    Re-validated byte-for-byte across representative families spanning every 
enum
    category (GRP/resource-type+protocols, experiment state/type, per-protocol
    enum-keyed maps + monitor mode, data-product replica enums, compute-resource
    fileSystems map) — output identical with the hardcoded tables. manage.py 
check
    green; api test failures unchanged vs origin/main. Only the AuthzToken auth
    carrier + test fixtures still reference Thrift types (separate follow-ups 
before
    the dep drop).
---
 .../django_airavata/apps/api/serializers.py        | 186 +++++++++++----------
 .../django_airavata/apps/api/views.py              |  79 ++++-----
 .../django_airavata/apps/workspace/views.py        |  11 +-
 .../django_airavata/context_processors.py          |   8 +-
 4 files changed, 153 insertions(+), 131 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 8a2054825..87ab82716 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -1,11 +1,10 @@
 import datetime
+import enum
 import json
 import logging
 import os
 from urllib.parse import quote
 
-from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
-from airavata.model.group.ttypes import ResourcePermissionType
 from airavata_django_portal_sdk import (
     experiment_util
 )
@@ -23,6 +22,45 @@ log = logging.getLogger(__name__)
 TMP_INPUT_FILE_UPLOAD_DIR = "tmp"
 
 
+# Historical Thrift enum member -> integer tables. The REST contract emits
+# these Thrift enum integers (e.g. dataProductType FILE == 0), which differ 
from
+# the proto enum integers; the proto-native serializers bridge by member NAME 
via
+# proto_enum_int_field / proto_enum_keyed_map_field. Hardcoded so the portal no
+# longer depends on the legacy Thrift model types for the enum values.
+_THRIFT_ENUM_INTS = {
+    'ApplicationParallelismType': {'CCM': 4, 'CRAY_MPI': 5, 'MPI': 1, 
'OPENMP': 2, 'OPENMP_MPI': 3, 'SERIAL': 0},
+    'DataMovementProtocol': {'GridFTP': 3, 'LOCAL': 0, 'SCP': 1, 'SFTP': 2, 
'UNICORE_STORAGE_SERVICE': 4},
+    'DataProductType': {'COLLECTION': 1, 'FILE': 0},
+    'DataType': {'FLOAT': 2, 'INTEGER': 1, 'STDERR': 6, 'STDOUT': 5, 'STRING': 
0, 'URI': 3, 'URI_COLLECTION': 4},
+    'ExperimentState': {'CANCELED': 6, 'CANCELING': 5, 'COMPLETED': 7, 
'CREATED': 0, 'EXECUTING': 4, 'FAILED': 8, 'LAUNCHED': 3, 'SCHEDULED': 2, 
'VALIDATED': 1},
+    'ExperimentType': {'SINGLE_APPLICATION': 0, 'WORKFLOW': 1},
+    'FileSystems': {'ARCHIVE': 4, 'HOME': 0, 'LOCALTMP': 2, 'SCRATCH': 3, 
'WORK': 1},
+    'IOType': {'FILE': 0, 'PROPERTY': 1},
+    'JobManagerCommand': {'CHECK_JOB': 3, 'DELETION': 2, 'JOB_MONITORING': 1, 
'SHOW_CLUSTER_INFO': 7, 'SHOW_NO_OF_PENDING_JOBS': 9, 
'SHOW_NO_OF_RUNNING_JOBS': 8, 'SHOW_QUEUE': 4, 'SHOW_RESERVATION': 5, 
'SHOW_START': 6, 'SUBMISSION': 0},
+    'JobState': {'ACTIVE': 2, 'CANCELED': 4, 'COMPLETE': 3, 'FAILED': 5, 
'NON_CRITICAL_FAIL': 8, 'QUEUED': 1, 'SUBMITTED': 0, 'SUSPENDED': 6, 'UNKNOWN': 
7},
+    'JobSubmissionProtocol': {'CLOUD': 4, 'GLOBUS': 2, 'LOCAL': 0, 
'LOCAL_FORK': 6, 'SSH': 1, 'SSH_FORK': 5, 'UNICORE': 3},
+    'MonitorMode': {'CLOUD_JOB_MONITOR': 1, 'FORK': 4, 
'JOB_EMAIL_NOTIFICATION_MONITOR': 2, 'LOCAL': 5, 'POLL_JOB_MANAGER': 0, 
'XSEDE_AMQP_SUBSCRIBE': 3},
+    'ProcessState': {'CANCELED': 13, 'CANCELLING': 12, 'COMPLETED': 10, 
'CONFIGURING_WORKSPACE': 4, 'CREATED': 0, 'DEQUEUING': 15, 'EXECUTING': 6, 
'FAILED': 11, 'INPUT_DATA_STAGING': 5, 'MONITORING': 7, 'OUTPUT_DATA_STAGING': 
8, 'POST_PROCESSING': 9, 'PRE_PROCESSING': 3, 'QUEUED': 14, 'REQUEUED': 16, 
'STARTED': 2, 'VALIDATED': 1},
+    'ProviderName': {'AWSEC2': 1, 'EC2': 0, 'RACKSPACE': 2},
+    'ReplicaLocationCategory': {'COMPUTE_RESOURCE': 1, 'GATEWAY_DATA_STORE': 
0, 'LONG_TERM_STORAGE_RESOURCE': 2, 'OTHER': 3},
+    'ReplicaPersistentType': {'PERSISTENT': 1, 'TRANSIENT': 0},
+    'ResourceJobManagerType': {'AIRAVATA_CUSTOM': 6, 'CLOUD': 5, 'FORK': 0, 
'HTCONDOR': 7, 'LSF': 3, 'PBS': 1, 'SLURM': 2, 'UGE': 4},
+    'ResourcePermissionType': {'MANAGE_SHARING': 3, 'OWNER': 2, 'READ': 1, 
'WRITE': 0},
+    'ResourceType': {'AWS': 1, 'SLURM': 0},
+    'SecurityProtocol': {'GSI': 2, 'KERBEROS': 3, 'LOCAL': 5, 'OAUTH': 4, 
'SSH_KEYS': 1, 'USERNAME_PASSWORD': 0},
+    'Status': {'ACTIVE': 0, 'APPROVED': 2, 'CONFIRMED': 1, 'DECLINED': 12, 
'DELETED': 3, 'DENIED': 7, 'DUPLICATE': 4, 'EXPIRED': 13, 'GRACE_PERIOD': 5, 
'INVITED': 6, 'PENDING': 8, 'PENDING_APPROVAL': 9, 'PENDING_CONFIRMATION': 10, 
'SUSPENDED': 11},
+    'TaskState': {'CANCELED': 4, 'COMPLETED': 2, 'CREATED': 0, 'EXECUTING': 1, 
'FAILED': 3},
+    'TaskTypes': {'DATA_STAGING': 1, 'ENV_CLEANUP': 3, 'ENV_SETUP': 0, 
'JOB_SUBMISSION': 2, 'MONITORING': 4, 'OUTPUT_FETCHING': 5},
+}
+
+
+# The share/revoke set logic uses these as comparable sentinels (the sharing
+# facade is addressed by the member NAME, e.g. "WRITE"); a plain IntEnum 
mirroring
+# the historical Thrift values keeps the portal free of the Thrift model type.
+ResourcePermissionType = enum.IntEnum(
+    'ResourcePermissionType', _THRIFT_ENUM_INTS['ResourcePermissionType'])
+
+
 def user_has_access(request, resource_id, permission="WRITE"):
     """gRPC sharing access check (Track D — replaces the Thrift userHasAccess).
 
@@ -202,11 +240,12 @@ class ProtoEnumIntField(serializers.Field):
         return self._thrift_to_proto.get(int(data), 0)
 
 
-def proto_enum_int_field(enum_descriptor, thrift_enum, proto_prefix='',
+def proto_enum_int_field(enum_descriptor, thrift_ints, proto_prefix='',
                          name_map=None, **kwargs):
-    """Build a :class:`ProtoEnumIntField` bridging a proto enum to the Thrift 
enum
-    integer by member NAME (stripping ``proto_prefix`` from proto-namespaced
-    members). Members absent from the Thrift enum map to ``None``.
+    """Build a :class:`ProtoEnumIntField` bridging a proto enum to the 
historical
+    Thrift enum INTEGER by member NAME (stripping ``proto_prefix`` from
+    proto-namespaced members). ``thrift_ints`` is the ``{member name: thrift 
int}``
+    table (see :data:`_THRIFT_ENUM_INTS`); members absent from it map to 
``None``.
 
     ``name_map`` overrides the proto-member-name -> Thrift-member-name mapping 
for
     enums whose member names diverge beyond a simple prefix (proto3 
namespacing of
@@ -223,10 +262,10 @@ def proto_enum_int_field(enum_descriptor, thrift_enum, 
proto_prefix='',
             name = v.name
             if proto_prefix and name.startswith(proto_prefix):
                 name = name[len(proto_prefix):]
-        thrift_member = getattr(thrift_enum, name, None)
-        if thrift_member is not None:
-            proto_to_thrift[v.number] = int(thrift_member)
-            thrift_to_proto[int(thrift_member)] = v.number
+        thrift_int = thrift_ints.get(name)
+        if thrift_int is not None:
+            proto_to_thrift[v.number] = thrift_int
+            thrift_to_proto[thrift_int] = v.number
         else:
             proto_to_thrift[v.number] = None
     return ProtoEnumIntField(
@@ -244,15 +283,13 @@ _DATA_MOVEMENT_PROTOCOL_NAME_MAP = 
{'DATA_MOVEMENT_PROTOCOL_LOCAL': 'LOCAL'}
 def job_submission_protocol_field(**kwargs):
     """A :class:`ProtoEnumIntField` rendering proto ``JobSubmissionProtocol`` 
as
     the Thrift integer (proto ``JSP_CLOUD`` -> Thrift ``CLOUD``)."""
-    from airavata.model.appcatalog.computeresource.ttypes import (
-        JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
-    )
     from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
         compute_resource_pb2,
     )
     return proto_enum_int_field(
         compute_resource_pb2.JobSubmissionProtocol.DESCRIPTOR,
-        _ThriftJobSubmissionProtocol, proto_prefix='JOB_SUBMISSION_PROTOCOL_',
+        _THRIFT_ENUM_INTS['JobSubmissionProtocol'],
+        proto_prefix='JOB_SUBMISSION_PROTOCOL_',
         name_map=_JOB_SUBMISSION_PROTOCOL_NAME_MAP, **kwargs)
 
 
@@ -260,15 +297,13 @@ def data_movement_protocol_field(**kwargs):
     """A :class:`ProtoEnumIntField` rendering proto ``DataMovementProtocol`` as
     the Thrift integer (proto ``DATA_MOVEMENT_PROTOCOL_LOCAL`` -> Thrift 
``LOCAL``;
     proto-only ``GRID_FTP`` -> None)."""
-    from airavata.model.data.movement.ttypes import (
-        DataMovementProtocol as _ThriftDataMovementProtocol,
-    )
     from airavata_sdk.generated.org.apache.airavata.model.data.movement import 
(
         data_movement_pb2,
     )
     return proto_enum_int_field(
         data_movement_pb2.DataMovementProtocol.DESCRIPTOR,
-        _ThriftDataMovementProtocol, proto_prefix='DATA_MOVEMENT_PROTOCOL_',
+        _THRIFT_ENUM_INTS['DataMovementProtocol'],
+        proto_prefix='DATA_MOVEMENT_PROTOCOL_',
         name_map=_DATA_MOVEMENT_PROTOCOL_NAME_MAP, **kwargs)
 
 
@@ -289,16 +324,14 @@ class ProtoFileSystemsMapField(serializers.Field):
 
     def _key_map(self):
         if self._proto_to_thrift is None:
-            from airavata.model.appcatalog.computeresource.ttypes import (
-                FileSystems,
-            )
             from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
                 compute_resource_pb2,
             )
             proto_fs = compute_resource_pb2.FileSystems
+            thrift_fs = _THRIFT_ENUM_INTS['FileSystems']
             self._proto_to_thrift = {
-                proto_fs.Value(name): int(getattr(FileSystems, name))
-                for name in proto_fs.keys() if hasattr(FileSystems, name)
+                proto_fs.Value(name): thrift_fs[name]
+                for name in proto_fs.keys() if name in thrift_fs
             }
         return self._proto_to_thrift
 
@@ -330,15 +363,17 @@ class ProtoEnumKeyedMapField(serializers.Field):
             for k, v in value.items() if k in self._key_labels}
 
 
-def proto_enum_keyed_map_field(proto_enum_descriptor, thrift_enum, **kwargs):
+def proto_enum_keyed_map_field(proto_enum_descriptor, thrift_enum_name,
+                               thrift_ints, **kwargs):
     """Build a :class:`ProtoEnumKeyedMapField` mapping each proto enum int key 
to
-    ``str(Thrift enum member)`` by member name (proto and Thrift assign 
different
-    ints; unknown/zero-sentinel keys are dropped)."""
+    ``"<ThriftEnum>.<member>"`` — the string the old enum-keyed Thrift map's
+    ``DictField`` rendered (``str`` of a Thrift ``IntEnum`` member) — by member
+    name; members absent from ``thrift_ints`` (unknown/zero sentinels) are 
dropped.
+    """
     key_labels = {}
     for v in proto_enum_descriptor.values:
-        thrift_member = getattr(thrift_enum, v.name, None)
-        if thrift_member is not None:
-            key_labels[v.number] = str(thrift_member)
+        if v.name in thrift_ints:
+            key_labels[v.number] = "{}.{}".format(thrift_enum_name, v.name)
     return ProtoEnumKeyedMapField(key_labels=key_labels, **kwargs)
 
 
@@ -847,16 +882,13 @@ def _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)
+        _THRIFT_ENUM_INTS['ApplicationParallelismType'],
+        proto_prefix='APPLICATION_PARALLELISM_TYPE_', **kwargs)
 
 
 class CommandObjectSerializer(serializers.Serializer):
@@ -1110,51 +1142,42 @@ _MONITOR_MODE_NAME_MAP = {'MONITOR_FORK': 'FORK', 
'MONITOR_LOCAL': 'LOCAL'}
 
 
 def _security_protocol_field(**kwargs):
-    from airavata.model.data.movement.ttypes import (
-        SecurityProtocol as _ThriftSecurityProtocol,
-    )
     from airavata_sdk.generated.org.apache.airavata.model.data.movement import 
(
         data_movement_pb2,
     )
     return proto_enum_int_field(
-        data_movement_pb2.SecurityProtocol.DESCRIPTOR, _ThriftSecurityProtocol,
+        data_movement_pb2.SecurityProtocol.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['SecurityProtocol'],
         proto_prefix='SECURITY_PROTOCOL_', **kwargs)
 
 
 def _resource_job_manager_type_field(**kwargs):
-    from airavata.model.appcatalog.computeresource.ttypes import (
-        ResourceJobManagerType as _ThriftResourceJobManagerType,
-    )
     from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
         compute_resource_pb2,
     )
     return proto_enum_int_field(
         compute_resource_pb2.ResourceJobManagerType.DESCRIPTOR,
-        _ThriftResourceJobManagerType,
+        _THRIFT_ENUM_INTS['ResourceJobManagerType'],
         proto_prefix='RESOURCE_JOB_MANAGER_TYPE_', **kwargs)
 
 
 def _provider_name_field(**kwargs):
-    from airavata.model.appcatalog.computeresource.ttypes import (
-        ProviderName as _ThriftProviderName,
-    )
     from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
         compute_resource_pb2,
     )
     return proto_enum_int_field(
-        compute_resource_pb2.ProviderName.DESCRIPTOR, _ThriftProviderName,
+        compute_resource_pb2.ProviderName.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['ProviderName'],
         proto_prefix='PROVIDER_NAME_', **kwargs)
 
 
 def _monitor_mode_field(**kwargs):
-    from airavata.model.appcatalog.computeresource.ttypes import (
-        MonitorMode as _ThriftMonitorMode,
-    )
     from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
         compute_resource_pb2,
     )
     return proto_enum_int_field(
-        compute_resource_pb2.MonitorMode.DESCRIPTOR, _ThriftMonitorMode,
+        compute_resource_pb2.MonitorMode.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['MonitorMode'],
         name_map=_MONITOR_MODE_NAME_MAP, **kwargs)
 
 
@@ -1176,26 +1199,23 @@ class 
ResourceJobManagerSerializer(serializers.Serializer):
     parallelismPrefix = serializers.SerializerMethodField()
 
     def get_jobManagerCommands(self, pb):
-        from airavata.model.appcatalog.computeresource.ttypes import (
-            JobManagerCommand as _ThriftJobManagerCommand,
-        )
         from 
airavata_sdk.generated.org.apache.airavata.model.appcatalog.computeresource 
import (  # noqa: E501
             compute_resource_pb2,
         )
         return proto_enum_keyed_map_field(
             compute_resource_pb2.JobManagerCommand.DESCRIPTOR,
-            
_ThriftJobManagerCommand).to_representation(pb.job_manager_commands)
+            'JobManagerCommand', _THRIFT_ENUM_INTS['JobManagerCommand'],
+        ).to_representation(pb.job_manager_commands)
 
     def get_parallelismPrefix(self, pb):
-        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_keyed_map_field(
             parallelism_pb2.ApplicationParallelismType.DESCRIPTOR,
-            _ThriftParallelismType).to_representation(pb.parallelism_prefix)
+            'ApplicationParallelismType',
+            _THRIFT_ENUM_INTS['ApplicationParallelismType'],
+        ).to_representation(pb.parallelism_prefix)
 
 
 class LocalJobSubmissionSerializer(serializers.Serializer):
@@ -1305,30 +1325,28 @@ def _status_pb2():
 
 
 def _experiment_state_field(**kwargs):
-    from airavata.model.status.ttypes import ExperimentState as _T
     return proto_enum_int_field(
-        _status_pb2().ExperimentState.DESCRIPTOR, _T,
+        _status_pb2().ExperimentState.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['ExperimentState'],
         proto_prefix='EXPERIMENT_STATE_', **kwargs)
 
 
 def _process_state_field(**kwargs):
-    from airavata.model.status.ttypes import ProcessState as _T
     return proto_enum_int_field(
-        _status_pb2().ProcessState.DESCRIPTOR, _T,
+        _status_pb2().ProcessState.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['ProcessState'],
         proto_prefix='PROCESS_STATE_', **kwargs)
 
 
 def _task_state_field(**kwargs):
-    from airavata.model.status.ttypes import TaskState as _T
     return proto_enum_int_field(
-        _status_pb2().TaskState.DESCRIPTOR, _T,
+        _status_pb2().TaskState.DESCRIPTOR, _THRIFT_ENUM_INTS['TaskState'],
         proto_prefix='TASK_STATE_', **kwargs)
 
 
 def _job_state_field(**kwargs):
-    from airavata.model.status.ttypes import JobState as _T
     return proto_enum_int_field(
-        _status_pb2().JobState.DESCRIPTOR, _T,
+        _status_pb2().JobState.DESCRIPTOR, _THRIFT_ENUM_INTS['JobState'],
         proto_prefix='JOB_STATE_', **kwargs)
 
 
@@ -1487,10 +1505,10 @@ class _TaskModelSerializer(serializers.Serializer):
     currentRetry = serializers.IntegerField(source='current_retry', 
allow_null=True, required=False)
 
     def get_taskType(self, task):
-        from airavata.model.task.ttypes import TaskTypes as _T
         from airavata_sdk.generated.org.apache.airavata.model.task import 
task_pb2
         field = proto_enum_int_field(
-            task_pb2.TaskTypes.DESCRIPTOR, _T, proto_prefix='TASK_TYPES_')
+            task_pb2.TaskTypes.DESCRIPTOR, _THRIFT_ENUM_INTS['TaskTypes'],
+            proto_prefix='TASK_TYPES_')
         return field.to_representation(task.task_type)
 
 
@@ -1536,12 +1554,12 @@ class _ProcessModelSerializer(serializers.Serializer):
 
 
 def _experiment_type_field(**kwargs):
-    from airavata.model.experiment.ttypes import ExperimentType as _T
     from airavata_sdk.generated.org.apache.airavata.model.experiment import (
         experiment_pb2,
     )
     return proto_enum_int_field(
-        experiment_pb2.ExperimentType.DESCRIPTOR, _T,
+        experiment_pb2.ExperimentType.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['ExperimentType'],
         proto_prefix='EXPERIMENT_TYPE_', **kwargs)
 
 
@@ -1716,23 +1734,23 @@ def _replica_catalog_pb2():
 
 
 def _data_product_type_field(**kwargs):
-    from airavata.model.data.replica.ttypes import DataProductType as _T
     return proto_enum_int_field(
-        _replica_catalog_pb2().DataProductType.DESCRIPTOR, _T,
+        _replica_catalog_pb2().DataProductType.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['DataProductType'],
         proto_prefix='DATA_PRODUCT_TYPE_', **kwargs)
 
 
 def _replica_location_category_field(**kwargs):
-    from airavata.model.data.replica.ttypes import ReplicaLocationCategory as 
_T
     return proto_enum_int_field(
-        _replica_catalog_pb2().ReplicaLocationCategory.DESCRIPTOR, _T,
+        _replica_catalog_pb2().ReplicaLocationCategory.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['ReplicaLocationCategory'],
         proto_prefix='REPLICA_LOCATION_CATEGORY_', **kwargs)
 
 
 def _replica_persistent_type_field(**kwargs):
-    from airavata.model.data.replica.ttypes import ReplicaPersistentType as _T
     return proto_enum_int_field(
-        _replica_catalog_pb2().ReplicaPersistentType.DESCRIPTOR, _T,
+        _replica_catalog_pb2().ReplicaPersistentType.DESCRIPTOR,
+        _THRIFT_ENUM_INTS['ReplicaPersistentType'],
         proto_prefix='REPLICA_PERSISTENT_TYPE_', **kwargs)
 
 
@@ -1937,10 +1955,9 @@ def _user_profile_pb2():
 
 
 def _user_status_field(**kwargs):
-    from airavata.model.user.ttypes import Status as _T
     return proto_enum_int_field(
-        _user_profile_pb2().Status.DESCRIPTOR, _T, proto_prefix='STATUS_',
-        **kwargs)
+        _user_profile_pb2().Status.DESCRIPTOR, _THRIFT_ENUM_INTS['Status'],
+        proto_prefix='STATUS_', **kwargs)
 
 
 class UserProfileSerializer(serializers.Serializer):
@@ -2006,12 +2023,9 @@ def _grp_pb2():
 
 
 def _resource_type_field(**kwargs):
-    from airavata.model.appcatalog.groupresourceprofile.ttypes import (
-        ResourceType as _T,
-    )
     return proto_enum_int_field(
-        _grp_pb2().ResourceType.DESCRIPTOR, _T, proto_prefix='RESOURCE_TYPE_',
-        **kwargs)
+        _grp_pb2().ResourceType.DESCRIPTOR, _THRIFT_ENUM_INTS['ResourceType'],
+        proto_prefix='RESOURCE_TYPE_', **kwargs)
 
 
 class ComputeResourceReservationSerializer(serializers.Serializer):
@@ -2753,7 +2767,7 @@ class ParserInputSerializer(serializers.Serializer):
         source='parser_id', allow_blank=True, allow_null=True, required=False)
     # IOType renders as the Thrift integer (proto FILE=1 -> Thrift FILE=0).
     type = proto_enum_int_field(
-        _parser_pb2().IOType.DESCRIPTOR, _ThriftIOType, 'IO_TYPE_',
+        _parser_pb2().IOType.DESCRIPTOR, _THRIFT_ENUM_INTS['IOType'], 
'IO_TYPE_',
         required=False, allow_null=True)
 
 
@@ -2767,7 +2781,7 @@ class ParserOutputSerializer(serializers.Serializer):
     parserId = serializers.CharField(
         source='parser_id', allow_blank=True, allow_null=True, required=False)
     type = proto_enum_int_field(
-        _parser_pb2().IOType.DESCRIPTOR, _ThriftIOType, 'IO_TYPE_',
+        _parser_pb2().IOType.DESCRIPTOR, _THRIFT_ENUM_INTS['IOType'], 
'IO_TYPE_',
         required=False, allow_null=True)
 
 
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index 2ba84c324..9ec7880b7 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -7,10 +7,6 @@ import warnings
 from datetime import datetime, timedelta
 from urllib.parse import quote
 
-from airavata.model.experiment.ttypes import (
-    ExperimentSearchFields
-)
-from airavata.model.group.ttypes import ResourcePermissionType
 from airavata_django_portal_sdk import (
     experiment_util,
     queue_settings_calculators
@@ -336,9 +332,14 @@ class ExperimentSearchViewSet(mixins.ListModelMixin, 
GenericAPIBackedViewSet):
 
         # gRPC SearchExperiments takes filters as a map<string, string> keyed 
by
         # ExperimentSearchFields member name (the query-param key already is 
one).
+        from airavata_sdk.generated.org.apache.airavata.model.experiment 
import (
+            experiment_pb2,
+        )
+        valid_fields = {
+            v.name for v in 
experiment_pb2.ExperimentSearchFields.DESCRIPTOR.values}
         filters = {}
         for key, value in self.request.query_params.items():
-            if key in ExperimentSearchFields.__members__:
+            if key in valid_fields:
                 filters[key] = value
 
         class ExperimentSearchResultIterator(APIResultIterator):
@@ -1017,13 +1018,13 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
         # Load accessible users in order of permission precedence: users that
         # have WRITE permission should also have READ
         users.update(self._load_directly_accessible_users(
-            lookup_value, ResourcePermissionType.READ))
+            lookup_value, serializers.ResourcePermissionType.READ))
         users.update(self._load_directly_accessible_users(
-            lookup_value, ResourcePermissionType.WRITE))
+            lookup_value, serializers.ResourcePermissionType.WRITE))
         users.update(self._load_directly_accessible_users(
-            lookup_value, ResourcePermissionType.MANAGE_SHARING))
+            lookup_value, serializers.ResourcePermissionType.MANAGE_SHARING))
         owner_ids = self._load_directly_accessible_users(
-            lookup_value, ResourcePermissionType.OWNER)
+            lookup_value, serializers.ResourcePermissionType.OWNER)
         # Assume that there is one and only one DIRECT owner (there may be one
         # or more INDIRECT cascading owners, which would the owners of the
         # ancestor entities, but getAllDirectlyAccessibleUsers does not return
@@ -1037,11 +1038,11 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
                               'permissionType': users[user_id]})
         groups = {}
         groups.update(self._load_directly_accessible_groups(
-            lookup_value, ResourcePermissionType.READ))
+            lookup_value, serializers.ResourcePermissionType.READ))
         groups.update(self._load_directly_accessible_groups(
-            lookup_value, ResourcePermissionType.WRITE))
+            lookup_value, serializers.ResourcePermissionType.WRITE))
         groups.update(self._load_directly_accessible_groups(
-            lookup_value, ResourcePermissionType.MANAGE_SHARING))
+            lookup_value, serializers.ResourcePermissionType.MANAGE_SHARING))
         group_list = []
         for group_id in groups:
             group_list.append({'group': self._load_group(group_id),
@@ -1053,12 +1054,12 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
 
     def _load_accessible_users(self, entity_id, permission_type):
         users = self.request.airavata.sharing.get_all_accessible_users(
-            entity_id, ResourcePermissionType(permission_type).name)
+            entity_id, 
serializers.ResourcePermissionType(permission_type).name)
         return {user_id: permission_type for user_id in users}
 
     def _load_directly_accessible_users(self, entity_id, permission_type):
         users = 
self.request.airavata.sharing.get_all_directly_accessible_users(
-            entity_id, ResourcePermissionType(permission_type).name)
+            entity_id, 
serializers.ResourcePermissionType(permission_type).name)
         return {user_id: permission_type for user_id in users}
 
     def _load_user_profile(self, user_id):
@@ -1068,12 +1069,12 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
 
     def _load_accessible_groups(self, entity_id, permission_type):
         groups = self.request.airavata.sharing.get_all_accessible_groups(
-            entity_id, ResourcePermissionType(permission_type).name)
+            entity_id, 
serializers.ResourcePermissionType(permission_type).name)
         return {group_id: permission_type for group_id in groups}
 
     def _load_directly_accessible_groups(self, entity_id, permission_type):
         groups = 
self.request.airavata.sharing.get_all_directly_accessible_groups(
-            entity_id, ResourcePermissionType(permission_type).name)
+            entity_id, 
serializers.ResourcePermissionType(permission_type).name)
         return {group_id: permission_type for group_id in groups}
 
     def _load_group(self, group_id):
@@ -1084,70 +1085,70 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
         entity_id = shared_entity['entityId']
         if len(shared_entity['_user_grant_read_permission']) > 0:
             self._share_with_users(
-                entity_id, ResourcePermissionType.READ,
+                entity_id, serializers.ResourcePermissionType.READ,
                 shared_entity['_user_grant_read_permission'])
         if len(shared_entity['_user_grant_write_permission']) > 0:
             self._share_with_users(
-                entity_id, ResourcePermissionType.WRITE,
+                entity_id, serializers.ResourcePermissionType.WRITE,
                 shared_entity['_user_grant_write_permission'])
         if len(shared_entity['_user_grant_manage_sharing_permission']) > 0:
             self._share_with_users(
-                entity_id, ResourcePermissionType.MANAGE_SHARING,
+                entity_id, serializers.ResourcePermissionType.MANAGE_SHARING,
                 shared_entity['_user_grant_manage_sharing_permission'])
         if len(shared_entity['_user_revoke_read_permission']) > 0:
             self._revoke_from_users(
-                entity_id, ResourcePermissionType.READ,
+                entity_id, serializers.ResourcePermissionType.READ,
                 shared_entity['_user_revoke_read_permission'])
         if len(shared_entity['_user_revoke_write_permission']) > 0:
             self._revoke_from_users(
-                entity_id, ResourcePermissionType.WRITE,
+                entity_id, serializers.ResourcePermissionType.WRITE,
                 shared_entity['_user_revoke_write_permission'])
         if len(shared_entity['_user_revoke_manage_sharing_permission']) > 0:
             self._revoke_from_users(
-                entity_id, ResourcePermissionType.MANAGE_SHARING,
+                entity_id, serializers.ResourcePermissionType.MANAGE_SHARING,
                 shared_entity['_user_revoke_manage_sharing_permission'])
         if len(shared_entity['_group_grant_read_permission']) > 0:
             self._share_with_groups(
-                entity_id, ResourcePermissionType.READ,
+                entity_id, serializers.ResourcePermissionType.READ,
                 shared_entity['_group_grant_read_permission'])
         if len(shared_entity['_group_grant_write_permission']) > 0:
             self._share_with_groups(
-                entity_id, ResourcePermissionType.WRITE,
+                entity_id, serializers.ResourcePermissionType.WRITE,
                 shared_entity['_group_grant_write_permission'])
         if len(shared_entity['_group_grant_manage_sharing_permission']) > 0:
             self._share_with_groups(
-                entity_id, ResourcePermissionType.MANAGE_SHARING,
+                entity_id, serializers.ResourcePermissionType.MANAGE_SHARING,
                 shared_entity['_group_grant_manage_sharing_permission'])
         if len(shared_entity['_group_revoke_read_permission']) > 0:
             self._revoke_from_groups(
-                entity_id, ResourcePermissionType.READ,
+                entity_id, serializers.ResourcePermissionType.READ,
                 shared_entity['_group_revoke_read_permission'])
         if len(shared_entity['_group_revoke_write_permission']) > 0:
             self._revoke_from_groups(
-                entity_id, ResourcePermissionType.WRITE,
+                entity_id, serializers.ResourcePermissionType.WRITE,
                 shared_entity['_group_revoke_write_permission'])
         if len(shared_entity['_group_revoke_manage_sharing_permission']) > 0:
             self._revoke_from_groups(
-                entity_id, ResourcePermissionType.MANAGE_SHARING,
+                entity_id, serializers.ResourcePermissionType.MANAGE_SHARING,
                 shared_entity['_group_revoke_manage_sharing_permission'])
 
     def _share_with_users(self, entity_id, permission_type, user_ids):
-        name = ResourcePermissionType(permission_type).name
+        name = serializers.ResourcePermissionType(permission_type).name
         self.request.airavata.sharing.share_resource_with_users(
             entity_id, {user_id: name for user_id in user_ids})
 
     def _revoke_from_users(self, entity_id, permission_type, user_ids):
-        name = ResourcePermissionType(permission_type).name
+        name = serializers.ResourcePermissionType(permission_type).name
         self.request.airavata.sharing.revoke_sharing_of_resource_from_users(
             entity_id, {user_id: name for user_id in user_ids})
 
     def _share_with_groups(self, entity_id, permission_type, group_ids):
-        name = ResourcePermissionType(permission_type).name
+        name = serializers.ResourcePermissionType(permission_type).name
         self.request.airavata.sharing.share_resource_with_groups(
             entity_id, {group_id: name for group_id in group_ids})
 
     def _revoke_from_groups(self, entity_id, permission_type, group_ids):
-        name = ResourcePermissionType(permission_type).name
+        name = serializers.ResourcePermissionType(permission_type).name
         self.request.airavata.sharing.revoke_sharing_of_resource_from_groups(
             entity_id, {group_id: name for group_id in group_ids})
 
@@ -1181,13 +1182,13 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
         # Load accessible users in order of permission precedence: users that
         # have WRITE permission should also have READ
         users.update(self._load_accessible_users(
-            entity_id, ResourcePermissionType.READ))
+            entity_id, serializers.ResourcePermissionType.READ))
         users.update(self._load_accessible_users(
-            entity_id, ResourcePermissionType.WRITE))
+            entity_id, serializers.ResourcePermissionType.WRITE))
         users.update(self._load_accessible_users(
-            entity_id, ResourcePermissionType.MANAGE_SHARING))
+            entity_id, serializers.ResourcePermissionType.MANAGE_SHARING))
         owner_ids = self._load_accessible_users(
-            entity_id, ResourcePermissionType.OWNER)
+            entity_id, serializers.ResourcePermissionType.OWNER)
         # Assume that there is one and only one DIRECT owner (there may be one
         # or more INDIRECT cascading owners, which would the owners of the
         # ancestor entities, but getAllAccessibleUsers does not return
@@ -1201,11 +1202,11 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
                               'permissionType': users[user_id]})
         groups = {}
         groups.update(self._load_accessible_groups(
-            entity_id, ResourcePermissionType.READ))
+            entity_id, serializers.ResourcePermissionType.READ))
         groups.update(self._load_accessible_groups(
-            entity_id, ResourcePermissionType.WRITE))
+            entity_id, serializers.ResourcePermissionType.WRITE))
         groups.update(self._load_accessible_groups(
-            entity_id, ResourcePermissionType.MANAGE_SHARING))
+            entity_id, serializers.ResourcePermissionType.MANAGE_SHARING))
         group_list = []
         for group_id in groups:
             group_list.append({'group': self._load_group(group_id),
diff --git a/airavata-django-portal/django_airavata/apps/workspace/views.py 
b/airavata-django-portal/django_airavata/apps/workspace/views.py
index 54fbe55cd..eb5c19fa1 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/views.py
+++ b/airavata-django-portal/django_airavata/apps/workspace/views.py
@@ -3,7 +3,6 @@ import json
 import logging
 from urllib.parse import urlparse
 
-from airavata.model.application.io.ttypes import DataType
 from django.contrib.auth.decorators import login_required
 from django.shortcuts import render
 from django.utils.module_loading import import_string
@@ -84,9 +83,15 @@ def create_experiment(request, app_module_id):
         raise Exception("Failed to load application module data: {}".format(
             app_interface.data['detail']))
     user_input_values = {}
+    # The serialized application-input `type` is the DataType member NAME; the
+    # historical Thrift DataType integers are kept here so the comparisons 
below
+    # behave exactly as before (a string never equals an int, as was already 
the
+    # case with the Thrift IntEnum members).
+    DataType_URI = 3
+    DataType_STRING = 0
     for app_input in app_interface.data['applicationInputs']:
         if (app_input['type'] ==
-                DataType.URI and app_input['name'] in request.GET):
+                DataType_URI and app_input['name'] in request.GET):
             user_file_value = request.GET[app_input['name']]
             try:
                 user_file_url = urlparse(user_file_value)
@@ -105,7 +110,7 @@ def create_experiment(request, app_module_id):
                             f"Failed checking data product uri: {dp_uri}", 
extra={'request': request})
             except ValueError:
                 logger.exception(f"Invalid user file value: 
{user_file_value}", extra={'request': request})
-        elif (app_input['type'] == DataType.STRING and
+        elif (app_input['type'] == DataType_STRING and
               app_input['name'] in request.GET):
             name = app_input['name']
             user_input_values[name] = request.GET[name]
diff --git a/airavata-django-portal/django_airavata/context_processors.py 
b/airavata-django-portal/django_airavata/context_processors.py
index 993b0ae74..0042b5db6 100644
--- a/airavata-django-portal/django_airavata/context_processors.py
+++ b/airavata-django-portal/django_airavata/context_processors.py
@@ -23,15 +23,17 @@ _notification_priority_proto_to_thrift = None
 def _notification_priority(value):
     global _notification_priority_proto_to_thrift
     if _notification_priority_proto_to_thrift is None:
-        from airavata.model.workspace.ttypes import NotificationPriority
         from airavata_sdk.generated.org.apache.airavata.model.workspace import 
(
             workspace_pb2,
         )
         proto = workspace_pb2.NotificationPriority
+        # Historical Thrift NotificationPriority integers (LOW/NORMAL/HIGH =
+        # 0/1/2) the dashboard expects; proto assigns 1/2/3 (0 = UNKNOWN).
+        thrift_ints = {'LOW': 0, 'NORMAL': 1, 'HIGH': 2}
         _notification_priority_proto_to_thrift = {
-            v.number: int(getattr(NotificationPriority, v.name))
+            v.number: thrift_ints[v.name]
             for v in proto.DESCRIPTOR.values
-            if hasattr(NotificationPriority, v.name)
+            if v.name in thrift_ints
         }
     return _notification_priority_proto_to_thrift.get(value)
 


Reply via email to