This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch feat/sdk-facade-migration
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git

commit 26b8f879d8d8126dc7fa27d3f90b647668858e01
Author: yasithdev <[email protected]>
AuthorDate: Wed Apr 8 00:06:52 2026 -0500

    refactor: replace Thrift infrastructure with AiravataClient facade
    
    - Remove all Thrift pool classes, transport helpers, and connection pool
      instantiations from utils.py; replace with a single 
create_airavata_client()
      factory using the airavata_sdk.AiravataClient facade
    - Rewrite middleware.py: AiravataClientMiddleware now creates/closes an
      AiravataClient per request; remove profile_service_client middleware 
entirely
      (facade wraps all profile services)
    - Inline airavata-django-portal-commons dynamic_apps module into
      django_airavata.dynamic_apps to eliminate the external dependency
    - Update auth/middleware.py: replace Thrift camelCase calls
      (getGatewayGroups, getAllGroupsUserBelongs) with facade snake_case
      (iam.get_gateway_groups, sharing.get_all_groups_user_belongs); remove
      request.profile_service usage
    - Update auth/utils.py: remove AuthzToken Thrift import; return plain dicts
      instead (SDK handles auth via gRPC metadata)
    - Update context_processors.py: getAllNotifications -> 
research.get_all_notifications
    - Update requirements.txt: drop thrift, thrift_connector,
      airavata-django-portal-sdk, airavata-django-portal-commons; pin
      airavata-python-sdk==3.0.0
---
 .../django_airavata/apps/auth/middleware.py        |  18 +-
 .../django_airavata/apps/auth/utils.py             |  24 +-
 .../django_airavata/context_processors.py          |   4 +-
 .../django_airavata/dynamic_apps/__init__.py       |  53 +++
 .../dynamic_apps/context_processors.py             | 130 ++++++++
 .../django_airavata/dynamic_apps/urls.py           |  12 +
 .../django_airavata/middleware.py                  |  48 ++-
 airavata-django-portal/django_airavata/settings.py |  19 +-
 airavata-django-portal/django_airavata/urls.py     |   5 +-
 airavata-django-portal/django_airavata/utils.py    | 357 +--------------------
 airavata-django-portal/requirements.txt            |   6 +-
 11 files changed, 258 insertions(+), 418 deletions(-)

diff --git a/airavata-django-portal/django_airavata/apps/auth/middleware.py 
b/airavata-django-portal/django_airavata/apps/auth/middleware.py
index 53569325a..6c9a1f3fd 100644
--- a/airavata-django-portal/django_airavata/apps/auth/middleware.py
+++ b/airavata-django-portal/django_airavata/apps/auth/middleware.py
@@ -34,13 +34,12 @@ def authz_token_middleware(get_response):
 def set_admin_group_attributes(request, gateway_groups=None):
     """Set is_gateway_admin and is_read_only_gateway_admin request attrs."""
     if gateway_groups is None:
-        gateway_groups = 
request.airavata_client.getGatewayGroups(request.authz_token)
-        gateway_groups = copy.deepcopy(gateway_groups.__dict__)
-    admins_group_id = gateway_groups['adminsGroupId']
-    read_only_admins_group_id = gateway_groups['readOnlyAdminsGroupId']
-    group_manager_client = request.profile_service['group_manager']
-    group_memberships = group_manager_client.getAllGroupsUserBelongs(
-        request.authz_token, request.user.username + "@" + settings.GATEWAY_ID)
+        gateway_groups = request.airavata_client.iam.get_gateway_groups()
+    admins_group_id = gateway_groups.get('adminsGroupId') if 
isinstance(gateway_groups, dict) else gateway_groups.admins_group_id
+    read_only_admins_group_id = gateway_groups.get('readOnlyAdminsGroupId') if 
isinstance(gateway_groups, dict) else gateway_groups.read_only_admins_group_id
+    airavata_internal_user_id = request.user.username + "@" + 
settings.GATEWAY_ID
+    group_memberships = 
request.airavata_client.sharing.get_all_groups_user_belongs(
+        airavata_internal_user_id)
     group_ids = [group.id for group in group_memberships]
     request.is_gateway_admin = admins_group_id in group_ids
     request.is_read_only_gateway_admin = read_only_admins_group_id in group_ids
@@ -63,9 +62,8 @@ def gateway_groups_middleware(get_response):
             # Load the GatewayGroups and check if user is in the Admins and/or
             # Read Only Admins groups
             if not request.session.get('GATEWAY_GROUPS'):
-                gateway_groups = request.airavata_client.getGatewayGroups(
-                    request.authz_token)
-                gateway_groups_dict = copy.deepcopy(gateway_groups.__dict__)
+                gateway_groups = 
request.airavata_client.iam.get_gateway_groups()
+                gateway_groups_dict = copy.deepcopy(gateway_groups.__dict__) 
if hasattr(gateway_groups, '__dict__') else dict(gateway_groups)
                 request.session['GATEWAY_GROUPS'] = gateway_groups_dict
             set_admin_group_attributes(request, 
gateway_groups=request.session.get("GATEWAY_GROUPS"))
             # Gateway Admins are made 'superuser' in Django so they can edit
diff --git a/airavata-django-portal/django_airavata/apps/auth/utils.py 
b/airavata-django-portal/django_airavata/apps/auth/utils.py
index cfa53c042..15a1b224b 100644
--- a/airavata-django-portal/django_airavata/apps/auth/utils.py
+++ b/airavata-django-portal/django_airavata/apps/auth/utils.py
@@ -2,7 +2,6 @@
 
 import time
 
-from airavata.model.security.ttypes import AuthzToken
 from django.conf import settings
 from django.contrib.auth import authenticate
 from django.core.mail import EmailMessage
@@ -15,7 +14,13 @@ from . import models
 
 
 def get_authz_token(request, user=None, access_token=None):
-    """Construct AuthzToken instance from session; refresh token if needed."""
+    """Get an access token dict from session; refresh token if needed.
+
+    Returns a dict with 'accessToken', 'gatewayID', and 'userName' for
+    backwards compatibility with code that reads these fields. The SDK
+    handles auth via gRPC metadata, so this is mainly used for checking
+    whether the user is still authenticated.
+    """
     if access_token is not None:
         return _create_authz_token(request, user=user, 
access_token=access_token)
     elif is_request_access_token(request):
@@ -48,10 +53,10 @@ def get_service_account_authz_token():
         verify=verify)
 
     access_token = token.get('access_token')
-    return AuthzToken(
-        accessToken=access_token,
-        # This is a service account, so leaving out userName for now
-        claimsMap={'gatewayID': settings.GATEWAY_ID})
+    return {
+        'accessToken': access_token,
+        'claimsMap': {'gatewayID': settings.GATEWAY_ID},
+    }
 
 
 def _create_authz_token(request, user=None, access_token=None):
@@ -61,9 +66,10 @@ def _create_authz_token(request, user=None, 
access_token=None):
         user = request.user
     username = user.username
     gateway_id = settings.GATEWAY_ID
-    return AuthzToken(accessToken=access_token,
-                      claimsMap={'gatewayID': gateway_id,
-                                 'userName': username})
+    return {
+        'accessToken': access_token,
+        'claimsMap': {'gatewayID': gateway_id, 'userName': username},
+    }
 
 
 def _get_access_token_source(request):
diff --git a/airavata-django-portal/django_airavata/context_processors.py 
b/airavata-django-portal/django_airavata/context_processors.py
index 43cbfcf95..b33bfaffe 100644
--- a/airavata-django-portal/django_airavata/context_processors.py
+++ b/airavata-django-portal/django_airavata/context_processors.py
@@ -19,8 +19,8 @@ def get_notifications(request):
     if request.user.is_authenticated and hasattr(request, 'airavata_client'):
         unread_notifications = 0
         try:
-            notifications = request.airavata_client.getAllNotifications(
-                request.authz_token, settings.GATEWAY_ID)
+            notifications = 
request.airavata_client.research.get_all_notifications(
+                settings.GATEWAY_ID)
         except Exception:
             logger.warning("Failed to load notifications")
             notifications = []
diff --git a/airavata-django-portal/django_airavata/dynamic_apps/__init__.py 
b/airavata-django-portal/django_airavata/dynamic_apps/__init__.py
new file mode 100644
index 000000000..1ba67e3c8
--- /dev/null
+++ b/airavata-django-portal/django_airavata/dynamic_apps/__init__.py
@@ -0,0 +1,53 @@
+import logging
+from importlib import import_module
+from importlib_metadata import entry_points
+
+
+# AppConfig instances from custom Django apps
+CUSTOM_DJANGO_APPS = []
+
+logger = logging.getLogger(__name__)
+
+
+def load(installed_apps, entry_point_group="airavata.djangoapp"):
+    for entry_point in entry_points(group=entry_point_group):
+        custom_app_class = entry_point.load()
+        custom_app_instance = custom_app_class(
+            entry_point.name, import_module(entry_point.module)
+        )
+        CUSTOM_DJANGO_APPS.append(custom_app_instance)
+        # Create path to AppConfig class (otherwise the ready() method doesn't 
get
+        # called)
+        logger.info(f"adding dynamic Django app {entry_point.name}")
+        installed_apps.append("{}.{}".format(entry_point.module, 
entry_point.attr))
+
+
+def merge_setting_dict(default, custom_setting):
+    # FIXME: only handles dict settings, doesn't handle lists
+    if isinstance(custom_setting, dict):
+        for k in custom_setting.keys():
+            if k not in default:
+                default[k] = custom_setting[k]
+            else:
+                raise Exception(
+                    "Custom django app setting conflicts with "
+                    "key {} in {}".format(k, default)
+                )
+
+
+def merge_settings(settings_module):
+    for custom_django_app in CUSTOM_DJANGO_APPS:
+        if hasattr(custom_django_app, "merge_settings"):
+            custom_django_app.merge_settings(settings_module)
+        elif hasattr(custom_django_app, "settings"):
+            # This approach is deprecated, use 'merge_settings' instead
+            # Merge settings from custom Django apps
+            # NOTE: only handles WEBPACK_LOADER additions
+            print(
+                f"{type(custom_django_app).__name__}.settings attr is 
deprecated, use merge_settings instead"
+            )
+            s = custom_django_app.settings
+            merge_setting_dict(
+                getattr(settings_module, "WEBPACK_LOADER"),
+                getattr(s, "WEBPACK_LOADER", {}),
+            )
diff --git 
a/airavata-django-portal/django_airavata/dynamic_apps/context_processors.py 
b/airavata-django-portal/django_airavata/dynamic_apps/context_processors.py
new file mode 100644
index 000000000..dcaa2361e
--- /dev/null
+++ b/airavata-django-portal/django_airavata/dynamic_apps/context_processors.py
@@ -0,0 +1,130 @@
+import copy
+from importlib import import_module
+import logging
+import re
+
+from django_airavata.dynamic_apps import CUSTOM_DJANGO_APPS
+
+logger = logging.getLogger(__name__)
+
+
+def custom_app_registry(request):
+    """Put custom Django apps into the context."""
+    custom_apps = CUSTOM_DJANGO_APPS.copy()
+    custom_apps = [
+        _enhance_custom_app_config(app)
+        for app in custom_apps
+        if (getattr(app, "enabled", None) is None or app.enabled(request))
+    ]
+    custom_apps.sort(key=lambda app: app.verbose_name.lower())
+    current_custom_app = _get_current_app(request, custom_apps)
+    return {
+        "custom_apps": custom_apps,
+        "current_custom_app": current_custom_app,
+        "custom_app_nav": (
+            _get_app_nav(request, current_custom_app) if current_custom_app 
else None
+        ),
+    }
+
+
+def _enhance_custom_app_config(app):
+    """As necessary add default values for properties to custom AppConfigs."""
+    app.url_app_name = _get_url_app_name(app)
+    app.url_home = _get_url_home(app)
+    app.fa_icon_class = _get_fa_icon_class(app)
+    app.app_description = _get_app_description(app)
+    return app
+
+
+def _get_url_app_name(app_config):
+    """Return the urls namespace for the given AppConfig instance."""
+    urls = _get_app_urls(app_config)
+    return getattr(urls, "app_name", None)
+
+
+def _get_url_home(app_config):
+    """Get named URL of home page of app."""
+    if hasattr(app_config, "url_home"):
+        return app_config.url_home
+    else:
+        return _get_default_url_home(app_config)
+
+
+def _get_default_url_home(app_config):
+    """Return first url pattern as a default."""
+    urls = _get_app_urls(app_config)
+    app_name = _get_url_app_name(app_config)
+    logger.warning(
+        "Custom Django app {} has no URL namespace "
+        "defined".format(app_config.label)
+    )
+    first_named_url = None
+    for urlpattern in urls.urlpatterns:
+        if hasattr(urlpattern, "name"):
+            first_named_url = urlpattern.name
+            break
+    if not first_named_url:
+        raise Exception(f"{urls} has no named urls, can't figure out default 
home URL")
+    if app_name:
+        return app_name + ":" + first_named_url
+    else:
+        return first_named_url
+
+
+def _get_fa_icon_class(app_config):
+    """Return Font Awesome icon class to use for app."""
+    if hasattr(app_config, "fa_icon_class"):
+        return app_config.fa_icon_class
+    else:
+        return "fa-circle"
+
+
+def _get_app_description(app_config):
+    """Return brief description of app."""
+    return getattr(app_config, "app_description", None)
+
+
+def _get_app_urls(app_config):
+    return import_module(".urls", app_config.name)
+
+
+def _get_current_app(request, apps):
+    current_app = [
+        app
+        for app in apps
+        if request.resolver_match
+        and app.url_app_name == request.resolver_match.app_name
+    ]
+    return current_app[0] if len(current_app) > 0 else None
+
+
+def _get_app_nav(request, current_app):
+    if hasattr(current_app, "nav"):
+        # Copy and filter current_app's nav items
+        nav = [
+            item
+            for item in copy.copy(current_app.nav)
+            if "enabled" not in item or item["enabled"](request)
+        ]
+        # convert "/djangoapp/path/in/app" to "path/in/app"
+        app_path = "/".join(request.path.split("/")[2:])
+        for nav_item in nav:
+            if "active_prefixes" in nav_item:
+                if re.match("|".join(nav_item["active_prefixes"]), app_path):
+                    nav_item["active"] = True
+                else:
+                    nav_item["active"] = False
+            else:
+                # 'active_prefixes' is optional, and if not specified, assume
+                # current item is active
+                nav_item["active"] = True
+    else:
+        # Default to the home view in the app
+        nav = [
+            {
+                "label": current_app.verbose_name,
+                "icon": "fa " + current_app.fa_icon_class,
+                "url": current_app.url_home,
+            }
+        ]
+    return nav
diff --git a/airavata-django-portal/django_airavata/dynamic_apps/urls.py 
b/airavata-django-portal/django_airavata/dynamic_apps/urls.py
new file mode 100644
index 000000000..915b1eb4c
--- /dev/null
+++ b/airavata-django-portal/django_airavata/dynamic_apps/urls.py
@@ -0,0 +1,12 @@
+from django_airavata.dynamic_apps import CUSTOM_DJANGO_APPS
+from django.conf.urls import include
+from django.urls import path
+
+urlpatterns = []
+for custom_django_app in CUSTOM_DJANGO_APPS:
+    # Custom Django apps may define a url_prefix, otherwise label will be used
+    # as url prefix
+    url_prefix = getattr(custom_django_app, "url_prefix", 
custom_django_app.label)
+    urlpatterns.append(
+        path(f"{url_prefix}/", include(custom_django_app.name + ".urls"))
+    )
diff --git a/airavata-django-portal/django_airavata/middleware.py 
b/airavata-django-portal/django_airavata/middleware.py
index 8c18c81d7..44cc46737 100644
--- a/airavata-django-portal/django_airavata/middleware.py
+++ b/airavata-django-portal/django_airavata/middleware.py
@@ -1,10 +1,9 @@
 import logging
 
-import thrift
-import thrift.transport.TTransport
+from django.conf import settings
 from django.shortcuts import render
 
-from . import utils
+from .utils import create_airavata_client
 
 logger = logging.getLogger(__name__)
 
@@ -14,14 +13,21 @@ class AiravataClientMiddleware:
         self.get_response = get_response
 
     def __call__(self, request):
-        with utils.airavata_api_client_pool.connection() as airavata_client:
-            request.airavata_client = airavata_client
+        access_token = _get_access_token(request)
+        gateway_id = settings.GATEWAY_ID
+        request.airavata_client = create_airavata_client(access_token, 
gateway_id)
+        try:
             response = self.get_response(request)
-
+        except Exception as e:
+            logger.exception("Error during request processing")
+            raise
+        finally:
+            request.airavata_client.close()
         return response
 
     def process_exception(self, request, exception):
-        if isinstance(exception, 
thrift.transport.TTransport.TTransportException):
+        # Handle connection errors to the Airavata API server
+        if isinstance(exception, ConnectionError):
             return render(
                 request,
                 'django_airavata/error_page.html',
@@ -29,27 +35,11 @@ class AiravataClientMiddleware:
                 context={
                     'title': 'Airavata is down',
                     'text': """The Airavata API server is not reachable. 
Please try again."""})
-        else:
-            return None
-
-
-def profile_service_client(get_response):
-    """Open and close Profile Service client for each request.
+        return None
 
-    Usage:
-        request.profile_service['group_manager'].getGroup(
-            request.authz_token, groupId)
-    """
-
-    def middleware(request):
-        request.profile_service = {
-            'group_manager': utils.group_manager_client_pool,
-            'iam_admin': utils.iamadmin_client_pool,
-            'tenant_profile': utils.tenant_profile_client_pool,
-            'user_profile': utils.user_profile_client_pool,
-        }
-        response = get_response(request)
-
-        return response
 
-    return middleware
+def _get_access_token(request):
+    """Extract access token from request auth or session."""
+    if hasattr(request, 'auth') and request.auth is not None:
+        return request.auth
+    return request.session.get('ACCESS_TOKEN', '')
diff --git a/airavata-django-portal/django_airavata/settings.py 
b/airavata-django-portal/django_airavata/settings.py
index df495996a..68cc93e8a 100644
--- a/airavata-django-portal/django_airavata/settings.py
+++ b/airavata-django-portal/django_airavata/settings.py
@@ -13,7 +13,8 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
 import os
 import sys
 
-from airavata_django_portal_commons import dynamic_apps
+from django_airavata.dynamic_apps import load as _load_dynamic_apps
+from django_airavata.dynamic_apps import merge_settings as 
_merge_dynamic_settings
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -77,8 +78,6 @@ INSTALLED_APPS = [
     # django-webpack-loader
     'webpack_loader',
 
-    # Airavata Django Portal SDK
-    'airavata_django_portal_sdk',
 ]
 
 # List of app labels for Airavata apps that should be hidden from menus
@@ -95,9 +94,7 @@ MIDDLEWARE = [
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
     'django_airavata.apps.auth.middleware.authz_token_middleware',
     'django_airavata.middleware.AiravataClientMiddleware',
-    'django_airavata.middleware.profile_service_client',
-    # Needs to come after authz_token_middleware, airavata_client and
-    # profile_service_client
+    # Needs to come after authz_token_middleware and AiravataClientMiddleware
     'django_airavata.apps.auth.middleware.gateway_groups_middleware',
     # Wagtail related middleware
     'wagtail.contrib.redirects.middleware.RedirectMiddleware',
@@ -118,7 +115,7 @@ TEMPLATES = [
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
                 'django_airavata.context_processors.airavata_app_registry',
-                
'airavata_django_portal_commons.dynamic_apps.context_processors.custom_app_registry',
+                
'django_airavata.dynamic_apps.context_processors.custom_app_registry',
                 'django_airavata.context_processors.get_notifications',
                 'django_airavata.context_processors.user_session_data',
                 
'django_airavata.context_processors.google_analytics_tracking_id',
@@ -277,10 +274,6 @@ AUTHENTICATION_OPTIONS = {
 # for the access token parameter (defaults to 'access_token').
 ACCESS_TOKEN_REDIRECT_ALLOWED_URIS = []
 
-# Seconds each connection in the pool is able to stay alive. If open connection
-# has lived longer than this period, it will be closed.
-# (https://github.com/Thriftpy/thrift_connector)
-THRIFT_CLIENT_POOL_KEEPALIVE = 5
 
 # Webpack loader
 WEBPACK_LOADER = {
@@ -636,8 +629,8 @@ except ImportError:
 #        ...
 #    )
 #
-dynamic_apps.load(INSTALLED_APPS, "airavata.djangoapp")
+_load_dynamic_apps(INSTALLED_APPS, "airavata.djangoapp")
 
 # Merge WEBPACK_LOADER settings from custom Django apps
 settings_module = sys.modules[__name__]
-dynamic_apps.merge_settings(settings_module)
+_merge_dynamic_settings(settings_module)
diff --git a/airavata-django-portal/django_airavata/urls.py 
b/airavata-django-portal/django_airavata/urls.py
index 5c0dcf78d..552fc7342 100644
--- a/airavata-django-portal/django_airavata/urls.py
+++ b/airavata-django-portal/django_airavata/urls.py
@@ -32,8 +32,7 @@ urlpatterns = [
     re_path(r'^api/', include('django_airavata.apps.api.urls')),
     re_path(r'^groups/', include('django_airavata.apps.groups.urls')),
     re_path(r'^dataparsers/', 
include('django_airavata.apps.dataparsers.urls')),
-    path('sdk/', include('airavata_django_portal_sdk.urls')),
-    re_path(r'^home$', views.home, name='home'),
+re_path(r'^home$', views.home, name='home'),
     re_path(r'^cms/', include(wagtailadmin_urls)),
     re_path(r'^documents/', include(wagtaildocs_urls)),
     # For testing, developing error pages
@@ -41,7 +40,7 @@ urlpatterns = [
     re_path(r'^403/', views.error403),
     re_path(r'^404/', views.error404),
     re_path(r'^500/', views.error500),
-    path('', include('airavata_django_portal_commons.dynamic_apps.urls')),
+    path('', include('django_airavata.dynamic_apps.urls')),
     path('', include(wagtail_urls)),
 ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
diff --git a/airavata-django-portal/django_airavata/utils.py 
b/airavata-django-portal/django_airavata/utils.py
index a9576c739..8a23c2eb2 100644
--- a/airavata-django-portal/django_airavata/utils.py
+++ b/airavata-django-portal/django_airavata/utils.py
@@ -1,355 +1,18 @@
 import logging
-import ssl
-import queue
-from django.conf import settings
-from contextlib import contextmanager
 
-import thrift_connector.connection_pool as connection_pool
-from airavata.api import Airavata
-from airavata.service.profile.groupmanager.cpi import GroupManagerService
-from airavata.service.profile.groupmanager.cpi.constants import (
-    GROUP_MANAGER_CPI_NAME
-)
-from airavata.service.profile.iam.admin.services.cpi import IamAdminServices
-from airavata.service.profile.iam.admin.services.cpi.constants import (
-    IAM_ADMIN_SERVICES_CPI_NAME
-)
-from airavata.service.profile.tenant.cpi import TenantProfileService
-from airavata.service.profile.tenant.cpi.constants import (
-    TENANT_PROFILE_CPI_NAME
-)
-from airavata.service.profile.user.cpi import UserProfileService
-from airavata.service.profile.user.cpi.constants import USER_PROFILE_CPI_NAME
 from django.conf import settings
-from thrift.protocol import TBinaryProtocol
-from thrift.protocol.TMultiplexedProtocol import TMultiplexedProtocol
-from thrift.transport import TSocket, TSSLSocket, TTransport
-
-log = logging.getLogger(__name__)
-
-
-class ThriftConnectionException(Exception):
-    pass
-
-
-class ThriftClientException(Exception):
-    pass
-
-
-def get_unsecure_transport(hostname, port):
-    # Create a socket to the Airavata Server
-    transport = TSocket.TSocket(hostname, port)
 
-    # Use Buffered Protocol to speedup over raw sockets
-    transport = TTransport.TBufferedTransport(transport)
-    return transport
+from airavata_sdk import AiravataClient
 
+log = logging.getLogger(__name__)
 
-def get_secure_transport(hostname, port):
 
-    # Create a socket to the Airavata Server
-    transport = TSSLSocket.TSSLSocket(
-        hostname,
-        port,
-        cert_reqs=ssl.CERT_REQUIRED,
-        ca_certs=settings.CA_CERTS_PATH,
+def create_airavata_client(access_token, gateway_id):
+    """Create an AiravataClient instance for the given auth token."""
+    return AiravataClient(
+        host=settings.AIRAVATA_API_HOST,
+        port=settings.AIRAVATA_API_PORT,
+        token=access_token,
+        gateway_id=gateway_id,
+        secure=getattr(settings, 'AIRAVATA_API_SECURE', False),
     )
-    return TTransport.TBufferedTransport(transport)
-
-
-def get_transport(hostname, port, secure=True):
-    if secure:
-        transport = get_secure_transport(hostname, port)
-    else:
-        transport = get_unsecure_transport(hostname, port)
-    return transport
-
-
-def create_airavata_client(transport):
-
-    # Airavata currently uses Binary Protocol
-    protocol = TBinaryProtocol.TBinaryProtocol(transport)
-
-    # Create a Airavata client to use the protocol encoder
-    client = Airavata.Client(protocol)
-    return client
-
-
-def get_binary_protocol(transport):
-    return TBinaryProtocol.TBinaryProtocol(transport)
-
-
-def create_group_manager_client(transport):
-    protocol = get_binary_protocol(transport)
-    multiplex_prot = TMultiplexedProtocol(protocol, GROUP_MANAGER_CPI_NAME)
-    return GroupManagerService.Client(multiplex_prot)
-
-
-def create_iamadmin_client(transport):
-    protocol = get_binary_protocol(transport)
-    multiplex_prot = TMultiplexedProtocol(protocol,
-                                          IAM_ADMIN_SERVICES_CPI_NAME)
-    return IamAdminServices.Client(multiplex_prot)
-
-
-def create_tenant_profile_client(transport):
-    protocol = get_binary_protocol(transport)
-    multiplex_prot = TMultiplexedProtocol(protocol, TENANT_PROFILE_CPI_NAME)
-    return TenantProfileService.Client(multiplex_prot)
-
-
-def create_user_profile_client(transport):
-    protocol = get_binary_protocol(transport)
-    multiplex_prot = TMultiplexedProtocol(protocol, USER_PROFILE_CPI_NAME)
-    return UserProfileService.Client(multiplex_prot)
-
-
-def get_airavata_client():
-    """Get Airavata API client as context manager (use in `with statement`)."""
-    return get_thrift_client(settings.AIRAVATA_API_HOST,
-                             settings.AIRAVATA_API_PORT,
-                             settings.AIRAVATA_API_SECURE,
-                             create_airavata_client)
-
-
-def get_group_manager_client():
-    """Group Manager client as context manager (use in `with statement`)."""
-    return get_thrift_client(settings.PROFILE_SERVICE_HOST,
-                             settings.PROFILE_SERVICE_PORT,
-                             settings.PROFILE_SERVICE_SECURE,
-                             create_group_manager_client)
-
-
-def get_iam_admin_client():
-    """IAM Admin client as context manager (use in `with statement`)."""
-    return get_thrift_client(settings.PROFILE_SERVICE_HOST,
-                             settings.PROFILE_SERVICE_PORT,
-                             settings.PROFILE_SERVICE_SECURE,
-                             create_iamadmin_client)
-
-
-def get_tenant_profile_client():
-    """Tenant Profile client as context manager (use in `with statement`)."""
-    return get_thrift_client(settings.PROFILE_SERVICE_HOST,
-                             settings.PROFILE_SERVICE_PORT,
-                             settings.PROFILE_SERVICE_SECURE,
-                             create_tenant_profile_client)
-
-
-def get_user_profile_client():
-    """User Profile client as context manager (use in `with statement`)."""
-    return get_thrift_client(settings.PROFILE_SERVICE_HOST,
-                             settings.PROFILE_SERVICE_PORT,
-                             settings.PROFILE_SERVICE_SECURE,
-                             create_user_profile_client)
-
-
-@contextmanager
-def get_thrift_client(host, port, is_secure, client_generator):
-    transport = get_transport(host, port, is_secure)
-    client = client_generator(transport)
-
-    try:
-        transport.open()
-        log.debug("Thrift connection opened to {}:{}, "
-                  "secure={}".format(host, port, is_secure))
-        try:
-            yield client
-        except Exception as e:
-            log.exception("Thrift client error occurred")
-            raise ThriftClientException(
-                "Thrift client error occurred: " + str(e)) from e
-        finally:
-            if transport.isOpen():
-                transport.close()
-                log.debug("Thrift connection closed to {}:{}, "
-                          "secure={}".format(host, port, is_secure))
-    except ThriftClientException as tce:
-        # Allow thrift client errors to bubble up
-        raise tce
-    except Exception as e:
-        msg = "Failed to open thrift connection to {}:{}, secure={}".format(
-            host, port, is_secure)
-        log.debug(msg)
-        raise ThriftConnectionException(msg) from e
-
-
-@contextmanager
-def simple_thrift_connection(pool):
-    """Context manager for borrowing a connection from the pool."""
-    conn = pool.get_connection()
-    try:
-        yield conn['client']
-    finally:
-        pool.return_connection(conn)
-
-
-class SimpleThriftPool:
-    """
-    A thread-safe Thrift connection pool that uses raw Thrift and the 
TBufferedTransport.
-    """
-
-    def __init__(self, service, host, port, size=5, secure=False, 
ca_certs=None):
-        self._service = service
-        self._host = host
-        self._port = port
-        self._size = size
-        self._secure = secure
-        self._ca_certs = ca_certs or settings.CA_CERTS_PATH
-        self._pool = queue.Queue(maxsize=size)
-        self._initialize_pool()
-
-    def _initialize_pool(self):
-        for _ in range(self._size):
-            self._pool.put(self._create_connection())
-
-    def _create_connection(self):
-        if self._secure:
-            socket = TSSLSocket.TSSLSocket(
-                host=self._host,
-                port=self._port,
-                cert_reqs=ssl.CERT_REQUIRED,
-                ca_certs=self._ca_certs,
-            )
-        else:
-            socket = TSocket.TSocket(host=self._host, port=self._port)
-        transport = TTransport.TBufferedTransport(socket)
-        protocol = TBinaryProtocol.TBinaryProtocol(transport)
-        client = self._service.Client(protocol)
-        transport.open()
-        return {'client': client, 'transport': transport}
-
-    def get_connection(self):
-        conn = self._pool.get()
-        if not self._is_alive(conn):
-            log.debug("Stale connection detected, creating new one")
-            try:
-                conn['transport'].close()
-            except Exception:
-                pass
-            conn = self._create_connection()
-        return conn
-
-    def _is_alive(self, conn):
-        try:
-            if not conn['transport'].isOpen():
-                return False
-            conn['client'].getAPIVersion()
-            return True
-        except Exception:
-            return False
-
-    def return_connection(self, conn):
-        try:
-            if conn['transport'].isOpen():
-                conn['transport'].close()
-        except Exception:
-            pass
-        self._pool.put(self._create_connection())
-
-    def connection(self):
-        return simple_thrift_connection(self)
-
-
-class CustomThriftClient(connection_pool.ThriftClient):
-    secure = False
-    validate = False
-
-    @classmethod
-    def get_socket_factory(cls):
-        if not cls.secure:
-            return super().get_socket_factory()
-        else:
-            def factory(host, port):
-                return TSSLSocket.TSSLSocket(
-                    host,
-                    port,
-                    cert_reqs=ssl.CERT_REQUIRED,
-                    ca_certs=settings.CA_CERTS_PATH,
-                )
-
-            return factory
-
-    def ping(self):
-        try:
-            self.client.getAPIVersion()
-        except Exception as e:
-            log.debug("getAPIVersion failed: {}".format(str(e)))
-            raise
-
-
-class MultiplexThriftClientMixin:
-    service_name = None
-
-    @classmethod
-    def get_protoco_factory(cls):
-        def factory(transport):
-            protocol = TBinaryProtocol.TBinaryProtocol(transport)
-            multiplex_prot = TMultiplexedProtocol(protocol, cls.service_name)
-            return multiplex_prot
-        return factory
-
-
-class AiravataAPIThriftClient(CustomThriftClient):
-    secure = settings.AIRAVATA_API_SECURE
-
-
-class GroupManagerServiceThriftClient(MultiplexThriftClientMixin,
-                                      CustomThriftClient):
-    service_name = GROUP_MANAGER_CPI_NAME
-    secure = settings.PROFILE_SERVICE_SECURE
-
-
-class IAMAdminServiceThriftClient(MultiplexThriftClientMixin,
-                                  CustomThriftClient):
-    service_name = IAM_ADMIN_SERVICES_CPI_NAME
-    secure = settings.PROFILE_SERVICE_SECURE
-
-
-class TenantProfileServiceThriftClient(MultiplexThriftClientMixin,
-                                       CustomThriftClient):
-    service_name = TENANT_PROFILE_CPI_NAME
-    secure = settings.PROFILE_SERVICE_SECURE
-
-
-class UserProfileServiceThriftClient(MultiplexThriftClientMixin,
-                                     CustomThriftClient):
-    service_name = USER_PROFILE_CPI_NAME
-    secure = settings.PROFILE_SERVICE_SECURE
-
-
-airavata_api_client_pool = SimpleThriftPool(
-    Airavata,
-    settings.AIRAVATA_API_HOST,
-    settings.AIRAVATA_API_PORT,
-    secure=settings.AIRAVATA_API_SECURE,
-    ca_certs=settings.CA_CERTS_PATH,
-)
-group_manager_client_pool = connection_pool.ClientPool(
-    GroupManagerService,
-    settings.PROFILE_SERVICE_HOST,
-    settings.PROFILE_SERVICE_PORT,
-    connection_class=GroupManagerServiceThriftClient,
-    keepalive=settings.THRIFT_CLIENT_POOL_KEEPALIVE
-)
-iamadmin_client_pool = connection_pool.ClientPool(
-    IamAdminServices,
-    settings.PROFILE_SERVICE_HOST,
-    settings.PROFILE_SERVICE_PORT,
-    connection_class=IAMAdminServiceThriftClient,
-    keepalive=settings.THRIFT_CLIENT_POOL_KEEPALIVE
-)
-tenant_profile_client_pool = connection_pool.ClientPool(
-    TenantProfileService,
-    settings.PROFILE_SERVICE_HOST,
-    settings.PROFILE_SERVICE_PORT,
-    connection_class=TenantProfileServiceThriftClient,
-    keepalive=settings.THRIFT_CLIENT_POOL_KEEPALIVE
-)
-user_profile_client_pool = connection_pool.ClientPool(
-    UserProfileService,
-    settings.PROFILE_SERVICE_HOST,
-    settings.PROFILE_SERVICE_PORT,
-    connection_class=UserProfileServiceThriftClient,
-    keepalive=settings.THRIFT_CLIENT_POOL_KEEPALIVE
-)
diff --git a/airavata-django-portal/requirements.txt 
b/airavata-django-portal/requirements.txt
index 2ef07015d..c3c89eb95 100644
--- a/airavata-django-portal/requirements.txt
+++ b/airavata-django-portal/requirements.txt
@@ -2,8 +2,6 @@
 Django==3.2.18
 requests==2.25.1
 requests-oauthlib==0.7.0
-thrift==0.22.0
-thrift_connector==0.24
 djangorestframework==3.12.4
 django-webpack-loader==0.6.0
 logging-formatter-anticrlf==1.2
@@ -24,8 +22,6 @@ grpcio-tools==1.51.1 ; python_version >= "3.7"
 grpcio==1.48.2 ; python_version < "3.7"
 grpcio==1.53.2 ; python_version >= "3.7"
 
-airavata-django-portal-sdk==1.8.4
-airavata-python-sdk==2.2.7
-airavata-django-portal-commons==1.0.0
+airavata-python-sdk==3.0.0
 
 -e "."


Reply via email to