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 7bacd99d6 refactor(portal): make data-product serializers proto-native
(Track D) (#203)
7bacd99d6 is described below
commit 7bacd99d69a15b89a1dc2398b55c863eb1cadfe6
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 04:48:46 2026 -0400
refactor(portal): make data-product serializers proto-native (Track D)
(#203)
Rewrite DataProductSerializer + DataReplicaLocationSerializer to read the
gRPC
DataProductModel / DataReplicaLocationModel protobuf directly, emitting the
same
Thrift-named JSON keys.
- dataProductType / replicaLocationCategory / replicaPersistentType render
as
Thrift ints via the prefix-aligned enum field helpers; the serializer's
extra
modifiedTime maps to the proto last_modified_time alongside
lastModifiedTime;
optional proto strings -> null; the method fields (downloadURL /
isInputFileUpload
/ filesize / userHasWriteAccess) read proto fields.
- Repoint every data-product consumer to pass protobuf through directly:
DataProductView (get/put), the download/delete endpoints,
FullExperimentViewSet's
input/output data products, output_views._generate_data,
workspace.create_experiment,
and view_utils.DataProductSharedDirPermission.
- Move the data_product_file_path helper to view_utils (proto-reading);
remove the
grpc_adapters data_product / _data_replica_location /
data_product_file_path
funcs + the now-unused Thrift DataProductModel/DataReplicaLocationModel
and
replica-enum imports.
Validated byte-for-byte (full data product w/ replica, empty) vs the old
adapter+serializer path. manage.py check green; api test failures unchanged.
---
.../django_airavata/apps/api/grpc_adapters.py | 70 -------------
.../django_airavata/apps/api/output_views.py | 7 +-
.../django_airavata/apps/api/serializers.py | 116 ++++++++++++++++-----
.../django_airavata/apps/api/view_utils.py | 27 ++++-
.../django_airavata/apps/api/views.py | 41 ++++----
.../django_airavata/apps/workspace/views.py | 6 +-
6 files changed, 136 insertions(+), 131 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 a59bb8b62..829a76c98 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -16,11 +16,6 @@ from types import SimpleNamespace
from airavata.model.appcatalog.computeresource.ttypes import (
JobSubmissionProtocol as _ThriftJobSubmissionProtocol,
)
-from airavata.model.data.replica.ttypes import (
- DataProductType as _ThriftDataProductType,
- ReplicaLocationCategory as _ThriftReplicaLocationCategory,
- ReplicaPersistentType as _ThriftReplicaPersistentType,
-)
from airavata.model.data.movement.ttypes import (
DataMovementProtocol as _ThriftDataMovementProtocol,
)
@@ -105,71 +100,6 @@ def group(pb):
)
-def _data_replica_location(pb):
- """gRPC ``DataReplicaLocationModel`` -> serializer shape."""
- return SimpleNamespace(
- replicaId=pb.replica_id or None,
- productUri=pb.product_uri or None,
- replicaName=pb.replica_name or None,
- replicaDescription=pb.replica_description or None,
- creationTime=pb.creation_time or None,
- lastModifiedTime=pb.last_modified_time or None,
- validUntilTime=pb.valid_until_time or None,
- replicaLocationCategory=_thrift_enum_prefixed(
- pb, 'replica_location_category', _ThriftReplicaLocationCategory,
- 'REPLICA_LOCATION_CATEGORY_'),
- replicaPersistentType=_thrift_enum_prefixed(
- pb, 'replica_persistent_type', _ThriftReplicaPersistentType,
- 'REPLICA_PERSISTENT_TYPE_'),
- storageResourceId=pb.storage_resource_id or None,
- filePath=pb.file_path or None,
- replicaMetadata=dict(pb.replica_metadata),
- )
-
-
-def data_product_file_path(data_product):
- """First replica's ``filePath`` from an adapted data product, or None.
-
- The gRPC ``storage`` facade expects the FULL FILE PATH, absolute or
- ``~/``-prefixed (a bare relative path NPEs server-side, as ``resolvePath``
- expands ``~/`` to the storage root). Replica file paths are typically
- absolute (e.g. ``/storage/tmp/<file>``); a relative one is ``~/``-prefixed.
- Pass an adapted ``DataProductModel`` (``grpc_adapters.data_product``).
- """
- replicas = getattr(data_product, 'replicaLocations', None) or []
- if not replicas:
- return None
- file_path = getattr(replicas[0], 'filePath', None)
- if not file_path:
- return None
- if not (file_path.startswith('/') or file_path.startswith('~/')):
- file_path = '~/' + file_path
- return file_path
-
-
-def data_product(pb):
- """gRPC ``DataProductModel`` -> ``DataProductSerializer`` shape."""
- return SimpleNamespace(
- productUri=pb.product_uri,
- gatewayId=pb.gateway_id,
- parentProductUri=pb.parent_product_uri or None,
- productName=pb.product_name or None,
- productDescription=pb.product_description or None,
- ownerName=pb.owner_name or None,
- dataProductType=_thrift_enum_prefixed(
- pb, 'data_product_type', _ThriftDataProductType,
- 'DATA_PRODUCT_TYPE_'),
- productSize=pb.product_size or None,
- creationTime=pb.creation_time or None,
- # the serializer declares both modifiedTime and lastModifiedTime
- modifiedTime=pb.last_modified_time or None,
- lastModifiedTime=pb.last_modified_time or None,
- productMetadata=dict(pb.product_metadata),
- replicaLocations=[
- _data_replica_location(r) for r in pb.replica_locations],
- )
-
-
def user_profile(pb):
"""gRPC ``UserProfile`` -> ``UserProfileSerializer`` shape.
diff --git a/airavata-django-portal/django_airavata/apps/api/output_views.py
b/airavata-django-portal/django_airavata/apps/api/output_views.py
index 2820d34f8..0df73208f 100644
--- a/airavata-django-portal/django_airavata/apps/api/output_views.py
+++ b/airavata-django-portal/django_airavata/apps/api/output_views.py
@@ -14,7 +14,7 @@ from
airavata_sdk.generated.org.apache.airavata.model.application.io.application
from django.conf import settings
from nbconvert import HTMLExporter
-from . import grpc_adapters
+from . import view_utils
logger = logging.getLogger(__name__)
@@ -220,11 +220,10 @@ def _generate_data(request,
experiment_output.value.startswith("airavata-dp")):
data_product_uris = experiment_output.value.split(",")
data_products = map(
- lambda dpid: grpc_adapters.data_product(
- request.airavata.research.get_data_product(dpid)),
+ lambda dpid: request.airavata.research.get_data_product(dpid),
data_product_uris)
for data_product in data_products:
- file_path = grpc_adapters.data_product_file_path(data_product)
+ file_path = view_utils.data_product_file_path(data_product)
if file_path and request.airavata.storage.file_exists(file_path):
resp = request.airavata.storage.download_file(file_path)
output_file = io.BytesIO(resp.content)
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 35476867c..1ad7103f5 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -15,10 +15,6 @@ from airavata.model.appcatalog.groupresourceprofile.ttypes
import (
AwsComputeResourcePreference
)
from airavata.model.appcatalog.parser.ttypes import IOType as _ThriftIOType
-from airavata.model.data.replica.ttypes import (
- DataProductModel,
- DataReplicaLocationModel
-)
from airavata.model.group.ttypes import GroupModel, ResourcePermissionType
from airavata.model.status.ttypes import (
ExperimentState
@@ -1699,23 +1695,91 @@ def _user_configuration_request(d):
return e.UserConfigurationDataModel(**kwargs)
-class DataReplicaLocationSerializer(
- thrift_utils.create_serializer_class(DataReplicaLocationModel)):
- creationTime = UTCPosixTimestampDateTimeField()
- lastModifiedTime = UTCPosixTimestampDateTimeField()
+def _replica_catalog_pb2():
+ from airavata_sdk.generated.org.apache.airavata.model.data.replica import (
+ replica_catalog_pb2,
+ )
+ return replica_catalog_pb2
-class DataProductSerializer(
- thrift_utils.create_serializer_class(DataProductModel)):
- creationTime = UTCPosixTimestampDateTimeField()
- modifiedTime = UTCPosixTimestampDateTimeField()
- lastModifiedTime = UTCPosixTimestampDateTimeField()
- replicaLocations = DataReplicaLocationSerializer(many=True)
+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,
+ 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,
+ 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,
+ proto_prefix='REPLICA_PERSISTENT_TYPE_', **kwargs)
+
+
+class DataReplicaLocationSerializer(serializers.Serializer):
+ """Proto-native serializer for the gRPC ``DataReplicaLocationModel``."""
+
+ replicaId = serializers.CharField(source='replica_id', allow_blank=True,
allow_null=True, required=False)
+ productUri = serializers.CharField(source='product_uri', allow_blank=True,
allow_null=True, required=False)
+ replicaName = serializers.CharField(source='replica_name',
allow_blank=True, allow_null=True, required=False)
+ replicaDescription = serializers.CharField(source='replica_description',
allow_blank=True, allow_null=True, required=False)
+ creationTime = ProtoTimestampField(source='creation_time',
null_if_zero=True)
+ lastModifiedTime = ProtoTimestampField(source='last_modified_time',
null_if_zero=True)
+ validUntilTime = ProtoIntOrNoneField(source='valid_until_time')
+ replicaLocationCategory =
_replica_location_category_field(source='replica_location_category',
required=False, allow_null=True)
+ replicaPersistentType =
_replica_persistent_type_field(source='replica_persistent_type',
required=False, allow_null=True)
+ storageResourceId = serializers.CharField(source='storage_resource_id',
allow_blank=True, allow_null=True, required=False)
+ filePath = serializers.CharField(source='file_path', allow_blank=True,
allow_null=True, required=False)
+ replicaMetadata = serializers.DictField(source='replica_metadata',
required=False)
+
+ def to_representation(self, instance):
+ ret = super().to_representation(instance)
+ # The old adapter mapped these optional proto strings to None when
empty.
+ for f in ('replicaId', 'productUri', 'replicaName',
'replicaDescription',
+ 'storageResourceId', 'filePath'):
+ if ret.get(f) == '':
+ ret[f] = None
+ return ret
+
+
+class DataProductSerializer(serializers.Serializer):
+ """Proto-native serializer for the gRPC ``DataProductModel``."""
+
+ productUri = serializers.CharField(source='product_uri', allow_blank=True,
allow_null=True, required=False)
+ gatewayId = serializers.CharField(source='gateway_id', allow_blank=True,
allow_null=True, required=False)
+ parentProductUri = serializers.CharField(source='parent_product_uri',
allow_blank=True, allow_null=True, required=False)
+ productName = serializers.CharField(source='product_name',
allow_blank=True, allow_null=True, required=False)
+ productDescription = serializers.CharField(source='product_description',
allow_blank=True, allow_null=True, required=False)
+ ownerName = serializers.CharField(source='owner_name', allow_blank=True,
allow_null=True, required=False)
+ dataProductType = _data_product_type_field(source='data_product_type',
required=False, allow_null=True)
+ productSize = ProtoIntOrNoneField(source='product_size')
+ creationTime = ProtoTimestampField(source='creation_time',
null_if_zero=True)
+ # The serializer declares both modifiedTime and lastModifiedTime; both map
to
+ # the proto last_modified_time.
+ modifiedTime = ProtoTimestampField(source='last_modified_time',
null_if_zero=True)
+ lastModifiedTime = ProtoTimestampField(source='last_modified_time',
null_if_zero=True)
+ productMetadata = serializers.DictField(source='product_metadata',
required=False)
+ replicaLocations =
DataReplicaLocationSerializer(source='replica_locations', many=True,
required=False)
downloadURL = serializers.SerializerMethodField()
isInputFileUpload = serializers.SerializerMethodField()
filesize = serializers.SerializerMethodField()
userHasWriteAccess = serializers.SerializerMethodField()
+ def to_representation(self, instance):
+ ret = super().to_representation(instance)
+ for f in ('parentProductUri', 'productName', 'productDescription',
+ 'ownerName'):
+ if ret.get(f) == '':
+ ret[f] = None
+ return ret
+
def get_downloadURL(self, data_product):
"""Lazy portal URL to the byte-streaming download endpoint.
@@ -1723,11 +1787,11 @@ class DataProductSerializer(
is deferred to the endpoint, so this getter makes no backend call.
"""
request = self.context['request']
- if not getattr(data_product, 'replicaLocations', None):
+ if not data_product.replica_locations:
return None
base = request.build_absolute_uri(
reverse('django_airavata_api:download-file'))
- return base + '?data-product-uri=' + quote(data_product.productUri)
+ return base + '?data-product-uri=' + quote(data_product.product_uri)
def get_isInputFileUpload(self, data_product):
"""Return True if this is an uploaded input file.
@@ -1737,17 +1801,17 @@ class DataProductSerializer(
(TMP_INPUT_FILE_UPLOAD_DIR == "tmp"), so the first replica's file path
has that directory as its immediate parent.
"""
- replicas = getattr(data_product, 'replicaLocations', None) or []
- if not replicas or not replicas[0].filePath:
+ replicas = data_product.replica_locations
+ if not replicas or not replicas[0].file_path:
return False
- parent = os.path.dirname(replicas[0].filePath)
+ parent = os.path.dirname(replicas[0].file_path)
return os.path.basename(parent) == TMP_INPUT_FILE_UPLOAD_DIR
def get_filesize(self, data_product):
- # productSize comes from the data product registry; no backend call.
- return getattr(data_product, 'productSize', None) or 0
+ # product_size comes from the data product registry; no backend call.
+ return data_product.product_size or 0
- def get_userHasWriteAccess(self, data_product: DataProductModel):
+ def get_userHasWriteAccess(self, data_product):
"""Whether the requesting user may write this data product.
Derived without a backend file-metadata call: the owner always has
@@ -1755,12 +1819,12 @@ class DataProductSerializer(
(a user's own private storage) write is allowed.
"""
request = self.context['request']
- owner = getattr(data_product, 'ownerName', None)
+ owner = data_product.owner_name
if owner and owner == request.user.username:
return True
- replicas = getattr(data_product, 'replicaLocations', None) or []
- if replicas and replicas[0].filePath:
- if view_utils.is_shared_path(replicas[0].filePath):
+ replicas = data_product.replica_locations
+ if replicas and replicas[0].file_path:
+ if view_utils.is_shared_path(replicas[0].file_path):
# Only admins can edit files in a shared directory.
return request.is_gateway_admin
return True
diff --git a/airavata-django-portal/django_airavata/apps/api/view_utils.py
b/airavata-django-portal/django_airavata/apps/api/view_utils.py
index e77c5db75..57003f837 100644
--- a/airavata-django-portal/django_airavata/apps/api/view_utils.py
+++ b/airavata-django-portal/django_airavata/apps/api/view_utils.py
@@ -14,8 +14,6 @@ from rest_framework.reverse import reverse
from rest_framework.utils.urls import remove_query_param, replace_query_param
from rest_framework.viewsets import GenericViewSet
-from . import grpc_adapters
-
logger = logging.getLogger(__name__)
@@ -271,13 +269,32 @@ class BaseSharedDirPermission(permissions.BasePermission):
return True
+def data_product_file_path(data_product):
+ """First replica's ``file_path`` from a proto ``DataProductModel``, or
None.
+
+ The gRPC ``storage`` facade expects the FULL FILE PATH, absolute or
+ ``~/``-prefixed (a bare relative path NPEs server-side, as ``resolvePath``
+ expands ``~/`` to the storage root). Replica file paths are typically
+ absolute (e.g. ``/storage/tmp/<file>``); a relative one is ``~/``-prefixed.
+ """
+ replicas = data_product.replica_locations
+ if not replicas:
+ return None
+ file_path = replicas[0].file_path
+ if not file_path:
+ return None
+ if not (file_path.startswith('/') or file_path.startswith('~/')):
+ file_path = '~/' + file_path
+ return file_path
+
+
class DataProductSharedDirPermission(BaseSharedDirPermission):
def get_path(self, request, view) -> str:
data_product_uri = request.query_params.get(
'data-product-uri', request.query_params.get('product-uri', ''))
- data_product = grpc_adapters.data_product(
- request.airavata.research.get_data_product(data_product_uri))
- file_path = grpc_adapters.data_product_file_path(data_product)
+ data_product = request.airavata.research.get_data_product(
+ data_product_uri)
+ file_path = data_product_file_path(data_product)
return file_path or ""
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index 509a0617e..682882a85 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -77,6 +77,10 @@ def _data_type_pb2():
return application_io_pb2
+# First replica's ~/-prefixed file path from a proto DataProductModel.
+_data_product_file_path = view_utils.data_product_file_path
+
+
def _storage_upload_and_register(request, dir_path, uploaded_file, name=None,
content_type=None, experiment_id=None):
"""Upload a file to user storage and register a data product for it (gRPC).
@@ -111,8 +115,7 @@ def _storage_upload_and_register(request, dir_path,
uploaded_file, name=None,
storage_resource_id=storage.get_default_storage_resource_id(),
content_type=content_type,
product_size=metadata.size))
- return grpc_adapters.data_product(
- request.airavata.research.get_data_product(product_uri))
+ return request.airavata.research.get_data_product(product_uri)
class GroupViewSet(APIBackedViewSet):
@@ -372,15 +375,13 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
lookup_value)
DT = _data_type_pb2().DataType
outputDataProducts = [
- grpc_adapters.data_product(
- self.request.airavata.research.get_data_product(output.value))
+ self.request.airavata.research.get_data_product(output.value)
for output in experimentModel.experiment_outputs
if (output.value and
output.value.startswith('airavata-dp') and
output.type in (DT.URI, DT.STDOUT, DT.STDERR))]
outputDataProducts += [
- grpc_adapters.data_product(
- self.request.airavata.research.get_data_product(dp))
+ self.request.airavata.research.get_data_product(dp)
for output in experimentModel.experiment_outputs
if (output.value and
output.type == DT.URI_COLLECTION)
@@ -397,15 +398,13 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
exp_output_views = output_views.get_output_views(
self.request, experimentModel, applicationInterface)
inputDataProducts = [
- grpc_adapters.data_product(
- self.request.airavata.research.get_data_product(inp.value))
+ self.request.airavata.research.get_data_product(inp.value)
for inp in experimentModel.experiment_inputs
if (inp.value and
inp.value.startswith('airavata-dp') and
inp.type in (DT.URI, DT.STDOUT, DT.STDERR))]
inputDataProducts += [
- grpc_adapters.data_product(
- self.request.airavata.research.get_data_product(dp))
+ self.request.airavata.research.get_data_product(dp)
for inp in experimentModel.experiment_inputs
if (inp.value and
inp.type == DT.URI_COLLECTION)
@@ -825,25 +824,23 @@ class DataProductView(APIView):
def get(self, request, format=None):
data_product_uri = request.query_params['product-uri']
- data_product = grpc_adapters.data_product(
- request.airavata.research.get_data_product(data_product_uri))
+ data_product =
request.airavata.research.get_data_product(data_product_uri)
serializer = self.serializer_class(
data_product, context={'request': request})
return Response(serializer.data)
def put(self, request, format=None):
data_product_uri = request.query_params['product-uri']
- data_product = grpc_adapters.data_product(
- request.airavata.research.get_data_product(data_product_uri))
+ data_product =
request.airavata.research.get_data_product(data_product_uri)
if request.data and "fileContentText" in request.data:
- file_path = grpc_adapters.data_product_file_path(data_product)
+ file_path = _data_product_file_path(data_product)
if file_path is None:
return Response(status=status.HTTP_400_BAD_REQUEST)
# Overwrite the file content in place at the replica's path.
request.airavata.storage.upload_file(
path=file_path,
content=request.data["fileContentText"].encode("utf-8"),
- name=data_product.productName or os.path.basename(file_path))
+ name=data_product.product_name or os.path.basename(file_path))
return self.get(request=request, format=format)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
@@ -909,17 +906,16 @@ def download(request):
"""
data_product_uri = request.GET.get('data-product-uri', '')
try:
- data_product = grpc_adapters.data_product(
- request.airavata.research.get_data_product(data_product_uri))
+ data_product =
request.airavata.research.get_data_product(data_product_uri)
except Exception as e:
log.warning("Failed to load DataProduct for {}".format(
data_product_uri), exc_info=True, extra={'request': request})
raise Http404("data product does not exist") from e
- file_path = grpc_adapters.data_product_file_path(data_product)
+ file_path = _data_product_file_path(data_product)
if file_path is None:
raise Http404("data product has no replica to download")
resp = request.airavata.storage.download_file(file_path)
- file_name = resp.name or data_product.productName or
os.path.basename(file_path)
+ file_name = resp.name or data_product.product_name or
os.path.basename(file_path)
response = FileResponse(
io.BytesIO(resp.content),
as_attachment=False,
@@ -935,8 +931,7 @@ def delete_file(request):
data_product_uri = request.GET.get('data-product-uri', '')
data_product = None
try:
- data_product = grpc_adapters.data_product(
- request.airavata.research.get_data_product(data_product_uri))
+ data_product =
request.airavata.research.get_data_product(data_product_uri)
except Exception as e:
log.warning("Failed to load DataProduct for {}"
.format(data_product_uri), exc_info=True)
@@ -945,7 +940,7 @@ def delete_file(request):
if (data_product.gatewayId != settings.GATEWAY_ID or
data_product.ownerName != request.user.username):
raise PermissionDenied()
- file_path = grpc_adapters.data_product_file_path(data_product)
+ file_path = _data_product_file_path(data_product)
if file_path is None:
raise Http404("data product has no replica to delete")
request.airavata.storage.delete_file(file_path)
diff --git a/airavata-django-portal/django_airavata/apps/workspace/views.py
b/airavata-django-portal/django_airavata/apps/workspace/views.py
index 64120977d..54fbe55cd 100644
--- a/airavata-django-portal/django_airavata/apps/workspace/views.py
+++ b/airavata-django-portal/django_airavata/apps/workspace/views.py
@@ -9,7 +9,7 @@ from django.shortcuts import render
from django.utils.module_loading import import_string
from rest_framework.renderers import JSONRenderer
-from django_airavata.apps.api import grpc_adapters, models
+from django_airavata.apps.api import models, view_utils
from django_airavata.apps.api.views import (
ApplicationModuleViewSet,
ExperimentSearchViewSet,
@@ -93,9 +93,9 @@ def create_experiment(request, app_module_id):
if user_file_url.scheme == 'airavata-dp':
dp_uri = user_file_value
try:
- data_product = grpc_adapters.data_product(
+ data_product = (
request.airavata.research.get_data_product(dp_uri))
- file_path = grpc_adapters.data_product_file_path(
+ file_path = view_utils.data_product_file_path(
data_product)
if file_path and request.airavata.storage.file_exists(
file_path):