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 e4ef556f4dafc8636c0fc85b852cc05a9b510b1d
Author: yasithdev <[email protected]>
AuthorDate: Wed Apr 8 02:01:25 2026 -0500

    fix: resolve ty type errors, add AiravataRequest type stub
    
    - Create django_airavata/types.py with AiravataRequest extending HttpRequest
    - Fix method override signatures in proto_utils.py and serializers.py to 
match
      parent parameter names (value/data)
    - Add type annotations to proto_compat classes 
(GroupComputeResourcePreference,
      EnvironmentSpecificPreferences, ApplicationInterfaceDescription, 
GroupModel)
    - Add missing ResourcePermissionType.OWNER enum member
    - Fix real bugs: logger.exception extra={set} -> {dict}, quote(None) -> 
quote('')
    - Add assert guards for Optional->non-Optional narrowing in views.py
    - Widen dict type annotations to dict[str, Any] where mixed-type values 
stored
    - Add DecimalField max_digits/decimal_places defaults in proto_utils
    - Suppress unresolvable third-party imports with ty: ignore comments
    - Use Any for duck-typed request parameters in 
user_storage/helpers/middleware
    
    Reduces ty errors from 428 to 256 (remaining are Django ORM/DRF stubs 
issues).
---
 airavata-django-portal/django_airavata/__init__.py |  2 +-
 .../django_airavata/apps/api/helpers.py            | 15 ++++----
 .../django_airavata/apps/api/output_views.py       | 11 +++---
 .../django_airavata/apps/api/proto_utils.py        | 22 +++++++----
 .../django_airavata/apps/api/serializers.py        | 17 ++++----
 .../django_airavata/apps/api/user_storage.py       | 45 +++++++++++-----------
 .../django_airavata/apps/api/view_utils.py         |  4 +-
 .../django_airavata/apps/api/views.py              | 15 +++++---
 .../django_airavata/apps/auth/backends.py          |  2 +-
 .../django_airavata/apps/auth/middleware.py        | 10 +++--
 .../apps/auth/tests/test_backends.py               |  6 +--
 .../apps/auth/tests/test_middleware.py             |  4 +-
 .../django_airavata/apps/auth/views.py             |  2 +-
 .../django_airavata/apps/groups/forms.py           |  4 +-
 .../django_airavata/context_processors.py          |  3 +-
 .../django_airavata/middleware.py                  |  5 ++-
 .../django_airavata/proto_compat.py                | 18 +++++++--
 airavata-django-portal/django_airavata/settings.py |  2 +-
 airavata-django-portal/django_airavata/types.py    | 20 ++++++++++
 airavata-django-portal/django_airavata/utils.py    |  2 +-
 .../django_airavata/wagtailapps/base/blocks.py     |  2 +-
 21 files changed, 127 insertions(+), 84 deletions(-)

diff --git a/airavata-django-portal/django_airavata/__init__.py 
b/airavata-django-portal/django_airavata/__init__.py
index 2085355a5..7d927d752 100644
--- a/airavata-django-portal/django_airavata/__init__.py
+++ b/airavata-django-portal/django_airavata/__init__.py
@@ -3,7 +3,7 @@ from django.conf import settings
 # Only use PyMySQL as the db driver when in a local dev environment
 if settings.DEBUG:
     try:
-        import pymysql
+        import pymysql  # ty: ignore[unresolved-import]
 
         pymysql.install_as_MySQLdb()
     except ImportError:
diff --git a/airavata-django-portal/django_airavata/apps/api/helpers.py 
b/airavata-django-portal/django_airavata/apps/api/helpers.py
index de7eef814..0db5e8421 100644
--- a/airavata-django-portal/django_airavata/apps/api/helpers.py
+++ b/airavata-django-portal/django_airavata/apps/api/helpers.py
@@ -3,7 +3,6 @@ from typing import Any
 
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist
-from django.http import HttpRequest
 
 from django_airavata.proto_compat import ResourcePermissionType
 
@@ -13,7 +12,7 @@ logger = logging.getLogger(__name__)
 
 
 class WorkspacePreferencesHelper:
-    def get(self, request: HttpRequest) -> models.WorkspacePreferences:
+    def get(self, request: Any) -> models.WorkspacePreferences:
         try:
             workspace_preferences = 
models.WorkspacePreferences.objects.get(username=request.user.username)
             self._check(request, workspace_preferences)
@@ -22,7 +21,7 @@ class WorkspacePreferencesHelper:
             workspace_preferences.save()
         return workspace_preferences
 
-    def _create_default(self, request: HttpRequest) -> 
models.WorkspacePreferences:
+    def _create_default(self, request: Any) -> models.WorkspacePreferences:
         workspace_preferences = 
models.WorkspacePreferences.create(request.user.username)
         most_recent_project = self._get_most_recent_project(request)
         workspace_preferences.most_recent_project_id = 
most_recent_project.projectID
@@ -32,7 +31,7 @@ class WorkspacePreferencesHelper:
         )
         return workspace_preferences
 
-    def _get_most_recent_project(self, request: HttpRequest) -> Any:
+    def _get_most_recent_project(self, request: Any) -> Any:
         "Return most recent writeable project."
         projects = 
request.airavata_client.research.get_user_projects(settings.GATEWAY_ID, 
request.user.username, -1, 0)
         for project in projects:
@@ -40,7 +39,7 @@ class WorkspacePreferencesHelper:
                 return project
         return None
 
-    def _get_first_group_resource_profile(self, request: HttpRequest) -> Any:
+    def _get_first_group_resource_profile(self, request: Any) -> Any:
         "Return first accessible group resource profile"
 
         group_resource_profiles = 
request.airavata_client.compute.get_group_resource_list(settings.GATEWAY_ID)
@@ -49,7 +48,7 @@ class WorkspacePreferencesHelper:
         else:
             return None
 
-    def _check(self, request: HttpRequest, prefs: models.WorkspacePreferences) 
-> None:
+    def _check(self, request: Any, prefs: models.WorkspacePreferences) -> None:
         "Validate preference values and update as needed."
         if not prefs.most_recent_project_id or not self._can_write(request, 
prefs.most_recent_project_id):
             most_recent_project = self._get_most_recent_project(request)
@@ -72,8 +71,8 @@ class WorkspacePreferencesHelper:
             prefs.most_recent_group_resource_profile_id = first_grp_id
             prefs.save()
 
-    def _can_write(self, request: HttpRequest, entity_id: str) -> bool:
+    def _can_write(self, request: Any, entity_id: str) -> bool:
         return request.airavata_client.sharing.user_has_access(entity_id, 
ResourcePermissionType.WRITE)
 
-    def _can_read(self, request: HttpRequest, entity_id: str) -> bool:
+    def _can_read(self, request: Any, entity_id: str) -> bool:
         return request.airavata_client.sharing.user_has_access(entity_id, 
ResourcePermissionType.READ)
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 46f1325cb..119bffa2b 100644
--- a/airavata-django-portal/django_airavata/apps/api/output_views.py
+++ b/airavata-django-portal/django_airavata/apps/api/output_views.py
@@ -10,7 +10,6 @@ import nbformat
 import papermill as pm
 from django_airavata.apps.api import user_storage
 from django.conf import settings
-from django.http import HttpRequest
 from nbconvert import HTMLExporter
 
 from django_airavata.proto_compat import DataType
@@ -28,7 +27,7 @@ class DefaultViewProvider:
     immediate = False
     name = "Default"
 
-    def generate_data(self, request: HttpRequest, experiment_output: Any, 
experiment: Any, output_file: Any = None, **kwargs: Any) -> dict[str, Any]:
+    def generate_data(self, request: Any, experiment_output: Any, experiment: 
Any, output_file: Any = None, **kwargs: Any) -> dict[str, Any]:
         return {}
 
 
@@ -37,7 +36,7 @@ class ParameterizedNotebookViewProvider:
     name = "Example Parameterized Notebook View"
     # test_output_file = os.path.join(BASE_DIR, "data", "Gaussian.log")
 
-    def generate_data(self, request: HttpRequest, experiment_output: Any, 
experiment: Any, output_file: Any = None, output_dir: str | None = None) -> 
dict[str, str]:
+    def generate_data(self, request: Any, experiment_output: Any, experiment: 
Any, output_file: Any = None, output_dir: str | None = None) -> dict[str, str]:
         # use papermill to generate the output notebook
         output_file_path = os.path.realpath(output_file.name)
         pm.execute_notebook(
@@ -57,7 +56,7 @@ class ParameterizedNotebookViewProvider:
 DEFAULT_VIEW_PROVIDERS: dict[str, Any] = {"default": DefaultViewProvider()}
 
 
-def get_output_views(request: HttpRequest, experiment: Any, 
application_interface: Any = None) -> dict[str, list[dict[str, Any]]]:
+def get_output_views(request: Any, experiment: Any, application_interface: Any 
= None) -> dict[str, list[dict[str, Any]]]:
     output_views: dict[str, list[dict[str, Any]]] = {}
     for output in experiment.experimentOutputs:
         output_views[output.name] = []
@@ -135,7 +134,7 @@ def 
_get_application_output_view_providers(application_interface: Any, output_na
     return []
 
 
-def generate_data(request: HttpRequest, output_view_provider_id: str, 
experiment_output_name: str, experiment_id: str, test_mode: bool = False, 
**kwargs: Any) -> dict[str, Any]:
+def generate_data(request: Any, output_view_provider_id: str, 
experiment_output_name: str, experiment_id: str, test_mode: bool = False, 
**kwargs: Any) -> dict[str, Any]:
     output_view_provider = _get_output_view_provider(output_view_provider_id)
     # TODO if output_view_provider is None, return 404
     experiment = request.airavata_client.research.get_experiment(experiment_id)
@@ -148,7 +147,7 @@ def generate_data(request: HttpRequest, 
output_view_provider_id: str, experiment
     return _generate_data(request, output_view_provider, experiment_output, 
experiment, test_mode=test_mode, **kwargs)
 
 
-def _generate_data(request: HttpRequest, output_view_provider: Any, 
experiment_output: Any, experiment: Any, test_mode: bool = False, **kwargs: 
Any) -> dict[str, Any]:
+def _generate_data(request: Any, output_view_provider: Any, experiment_output: 
Any, experiment: Any, test_mode: bool = False, **kwargs: Any) -> dict[str, Any]:
     output_files: list[Any] = []
     # test_mode can only be used in DEBUG=True mode
     if test_mode and settings.DEBUG:
diff --git a/airavata-django-portal/django_airavata/apps/api/proto_utils.py 
b/airavata-django-portal/django_airavata/apps/api/proto_utils.py
index add87d795..dc0952ec5 100644
--- a/airavata-django-portal/django_airavata/apps/api/proto_utils.py
+++ b/airavata-django-portal/django_airavata/apps/api/proto_utils.py
@@ -67,12 +67,12 @@ class UTCPosixTimestampDateTimeField(DateTimeField):
         self.initial = self.initial_value
         self.required = False
 
-    def to_representation(self, obj):
-        dt = datetime.datetime.fromtimestamp(obj / 1000, datetime.UTC)
+    def to_representation(self, value):  # type: ignore[override]
+        dt = datetime.datetime.fromtimestamp(value / 1000, datetime.UTC)
         return super().to_representation(dt)
 
-    def to_internal_value(self, data):
-        dt = super().to_internal_value(data)
+    def to_internal_value(self, value):  # type: ignore[override]
+        dt = super().to_internal_value(value)
         return int(dt.timestamp() * 1000)
 
     def initial_value(self):
@@ -87,10 +87,10 @@ class ThriftEnumField(Field):
         super().__init__(*args, **kwargs)
         self.enumClass = enumClass
 
-    def to_representation(self, obj):
-        if obj is None:
+    def to_representation(self, value):  # type: ignore[override]
+        if value is None:
             return None
-        return obj.name
+        return value.name
 
     def to_internal_value(self, data):
         if self.allow_null and data is None:
@@ -208,6 +208,9 @@ def process_field(field, enable_date_time_conversion, 
required=False, read_only=
             kwargs["allow_null"] = allow_null
         if field_class == CharField:
             kwargs["allow_blank"] = allow_null
+        if field_class == DecimalField:
+            kwargs["max_digits"] = 65
+            kwargs["decimal_places"] = 30
         thrift_model_class = mapping[field[1]]
 
         if (
@@ -242,6 +245,9 @@ def process_list_field(field):
         return ThriftEnumField(item_type_info)
 
     if item_ttype in mapping:
-        return mapping[item_ttype]()
+        field_cls = mapping[item_ttype]
+        if field_cls == DecimalField:
+            return field_cls(max_digits=65, decimal_places=30)
+        return field_cls()
     elif item_ttype == TTYPE_STRUCT:
         return create_serializer(item_type_info[0])
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py 
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index a016d5832..d7906f4ef 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -90,13 +90,13 @@ class 
UTCPosixTimestampDateTimeField(serializers.DateTimeField):
         self.initial = self.initial_value
         self.required = False
 
-    def to_representation(self, obj: int) -> str:
+    def to_representation(self, value):  # type: ignore[override]
         # Create datetime instance from milliseconds that is aware of timezon
-        dt = datetime.datetime.fromtimestamp(obj / 1000, datetime.UTC)
+        dt = datetime.datetime.fromtimestamp(value / 1000, datetime.UTC)
         return super().to_representation(dt)
 
-    def to_internal_value(self, data: str) -> int:
-        dt = super().to_internal_value(data)
+    def to_internal_value(self, value):  # type: ignore[override]
+        dt = super().to_internal_value(value)
         return int(dt.timestamp() * 1000)
 
     def initial_value(self) -> str:
@@ -124,6 +124,7 @@ class StoredJSONField(serializers.JSONField):
             return json.dumps(data)
         except (TypeError, ValueError):
             self.fail("invalid")
+            raise  # unreachable, but satisfies type checker
 
 
 class OrderedListField(serializers.ListField):
@@ -131,8 +132,8 @@ class OrderedListField(serializers.ListField):
         self.order_by = kwargs.pop("order_by", None)
         super().__init__(*args, **kwargs)
 
-    def to_representation(self, instance: list[Any]) -> list[dict[str, Any]] | 
None:
-        rep = super().to_representation(instance)
+    def to_representation(self, data):  # type: ignore[override]
+        rep = super().to_representation(data)
         if rep is not None:
             rep.sort(key=lambda item: item[self.order_by])
         return rep
@@ -281,7 +282,7 @@ class 
ApplicationModuleSerializer(proto_utils.create_serializer_class(Applicatio
 
 
 class EnumChoiceField(serializers.ChoiceField):
-    def __init__(self, enum_class: type, **kwargs: Any) -> None:
+    def __init__(self, enum_class: Any, **kwargs: Any) -> None:
         self.enum_class = enum_class
         kwargs["choices"] = [(member.name, member.name) for member in 
enum_class]
         super().__init__(**kwargs)
@@ -1804,7 +1805,7 @@ class 
UserHasWriteAccessToPathSerializer(serializers.Serializer):
                 if path != Path(""):
                     # get parent directory listing and use that to figure out 
if
                     # there is write access to this directory
-                    directories, _ = user_storage.listdir(request, path.parent)
+                    directories, _ = user_storage.listdir(request, 
str(path.parent))
                     for d in directories:
                         if Path(d["path"]) == path:
                             return d.get("userHasWriteAccess", False)
diff --git a/airavata-django-portal/django_airavata/apps/api/user_storage.py 
b/airavata-django-portal/django_airavata/apps/api/user_storage.py
index aed80ba33..b8d244443 100644
--- a/airavata-django-portal/django_airavata/apps/api/user_storage.py
+++ b/airavata-django-portal/django_airavata/apps/api/user_storage.py
@@ -11,7 +11,6 @@ import os
 from typing import Any, BinaryIO
 
 from django.conf import settings
-from django.http import HttpRequest
 
 log = logging.getLogger(__name__)
 
@@ -40,7 +39,7 @@ def _get_replica_storage_resource_id(data_product: Any) -> 
str | None:
 # File existence / metadata
 # ---------------------------------------------------------------------------
 
-def exists(request: HttpRequest, data_product: Any) -> bool:
+def exists(request: Any, data_product: Any) -> bool:
     """Check whether the file backing *data_product* exists in user storage."""
     path = _get_replica_filepath(data_product)
     if not path:
@@ -51,14 +50,14 @@ def exists(request: HttpRequest, data_product: Any) -> bool:
         return False
 
 
-def dir_exists(request: HttpRequest, path: str, experiment_id: str | None = 
None) -> bool:
+def dir_exists(request: Any, path: str, experiment_id: str | None = None) -> 
bool:
     """Check whether *path* exists as a directory in user storage."""
     if experiment_id:
         return experiment_dir_exists(request, experiment_id, path)
     return request.airavata_client.storage.dir_exists(path)
 
 
-def experiment_dir_exists(request: HttpRequest, experiment_id: str, path: str 
= "") -> bool:
+def experiment_dir_exists(request: Any, experiment_id: str, path: str = "") -> 
bool:
     """Check whether the experiment output directory exists."""
     try:
         request.airavata_client.storage.list_experiment_dir(experiment_id, 
path)
@@ -67,7 +66,7 @@ def experiment_dir_exists(request: HttpRequest, 
experiment_id: str, path: str =
         return False
 
 
-def is_input_file(request: HttpRequest, data_product: Any) -> bool:
+def is_input_file(request: Any, data_product: Any) -> bool:
     """Return True if the data product's path is under the inputs directory."""
     path = _get_replica_filepath(data_product)
     if not path:
@@ -81,7 +80,7 @@ def is_input_file(request: HttpRequest, data_product: Any) -> 
bool:
 # File / directory listing
 # ---------------------------------------------------------------------------
 
-def listdir(request: HttpRequest, path: str, experiment_id: str | None = None) 
-> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
+def listdir(request: Any, path: str, experiment_id: str | None = None) -> 
tuple[list[dict[str, Any]], list[dict[str, Any]]]:
     """List the contents of *path*, returning (directories, files) dicts."""
     if experiment_id:
         return list_experiment_dir(request, experiment_id, path)
@@ -91,7 +90,7 @@ def listdir(request: HttpRequest, path: str, experiment_id: 
str | None = None) -
     return directories, files
 
 
-def list_experiment_dir(request: HttpRequest, experiment_id: str, path: str = 
"") -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
+def list_experiment_dir(request: Any, experiment_id: str, path: str = "") -> 
tuple[list[dict[str, Any]], list[dict[str, Any]]]:
     """List the experiment output directory."""
     resp = request.airavata_client.storage.list_experiment_dir(experiment_id, 
path)
     directories = _metadata_list_to_dicts(resp.directories)
@@ -120,9 +119,10 @@ def _metadata_list_to_dicts(items: Any) -> list[dict[str, 
Any]]:
 # File open / download
 # ---------------------------------------------------------------------------
 
-def open_file(request: HttpRequest, data_product: Any) -> io.BytesIO:
+def open_file(request: Any, data_product: Any) -> io.BytesIO:
     """Download the file for *data_product* and return a file-like object."""
     path = _get_replica_filepath(data_product)
+    assert path is not None, "data_product has no replica file path"
     resp = request.airavata_client.storage.download_file(path)
     f = io.BytesIO(resp.content)
     f.name = resp.name or os.path.basename(path)
@@ -133,7 +133,7 @@ def open_file(request: HttpRequest, data_product: Any) -> 
io.BytesIO:
 # File upload / save
 # ---------------------------------------------------------------------------
 
-def save_input_file(request: HttpRequest, input_file: BinaryIO, name: str | 
None = None, content_type: str = "") -> Any:
+def save_input_file(request: Any, input_file: BinaryIO, name: str | None = 
None, content_type: str = "") -> Any:
     """Upload *input_file* to the user's input files directory.
 
     Returns a DataProductModel proto.
@@ -151,7 +151,7 @@ def save_input_file(request: HttpRequest, input_file: 
BinaryIO, name: str | None
     return request.airavata_client.research.get_data_product(resp.uri)
 
 
-def save(request: HttpRequest, path: str, file_obj: BinaryIO, name: str | None 
= None, content_type: str = "", experiment_id: str | None = None) -> Any:
+def save(request: Any, path: str, file_obj: BinaryIO, name: str | None = None, 
content_type: str = "", experiment_id: str | None = None) -> Any:
     """Upload *file_obj* to *path* in user storage.
 
     Returns a DataProductModel proto.
@@ -171,9 +171,10 @@ def save(request: HttpRequest, path: str, file_obj: 
BinaryIO, name: str | None =
 # File content update
 # ---------------------------------------------------------------------------
 
-def update_data_product_content(request: HttpRequest, data_product: Any, 
fileContentText: str) -> None:
+def update_data_product_content(request: Any, data_product: Any, 
fileContentText: str) -> None:
     """Replace the content of the file backing *data_product* with 
*fileContentText*."""
     path = _get_replica_filepath(data_product)
+    assert path is not None, "data_product has no replica file path"
     name = os.path.basename(path)
     request.airavata_client.storage.upload_file(
         path=os.path.dirname(path),
@@ -182,7 +183,7 @@ def update_data_product_content(request: HttpRequest, 
data_product: Any, fileCon
     )
 
 
-def update_file_content(request: HttpRequest, path: str, fileContentText: str) 
-> None:
+def update_file_content(request: Any, path: str, fileContentText: str) -> None:
     """Replace the content of the file at *path* with *fileContentText*."""
     name = os.path.basename(path)
     request.airavata_client.storage.upload_file(
@@ -196,30 +197,30 @@ def update_file_content(request: HttpRequest, path: str, 
fileContentText: str) -
 # File / directory creation and deletion
 # ---------------------------------------------------------------------------
 
-def create_user_dir(request: HttpRequest, path: str, experiment_id: str | None 
= None) -> tuple[None, str]:
+def create_user_dir(request: Any, path: str, experiment_id: str | None = None) 
-> tuple[None, str]:
     """Create a directory at *path*. Returns (storage_resource_id, 
created_path)."""
     resp = request.airavata_client.storage.create_dir(path)
     return None, resp.created_path
 
 
-def create_symlink(request: HttpRequest, source_path: str, dest_path: str) -> 
None:
+def create_symlink(request: Any, source_path: str, dest_path: str) -> None:
     """Create a symlink from *source_path* to *dest_path*."""
     request.airavata_client.storage.create_symlink(source_path, dest_path)
 
 
-def delete(request: HttpRequest, data_product: Any) -> None:
+def delete(request: Any, data_product: Any) -> None:
     """Delete the file backing *data_product*."""
     path = _get_replica_filepath(data_product)
     if path:
         request.airavata_client.storage.delete_file(path)
 
 
-def delete_user_file(request: HttpRequest, path: str, experiment_id: str | 
None = None) -> None:
+def delete_user_file(request: Any, path: str, experiment_id: str | None = 
None) -> None:
     """Delete a user file at *path*."""
     request.airavata_client.storage.delete_file(path)
 
 
-def delete_dir(request: HttpRequest, path: str, experiment_id: str | None = 
None) -> None:
+def delete_dir(request: Any, path: str, experiment_id: str | None = None) -> 
None:
     """Delete a directory at *path*."""
     request.airavata_client.storage.delete_dir(path)
 
@@ -228,7 +229,7 @@ def delete_dir(request: HttpRequest, path: str, 
experiment_id: str | None = None
 # File metadata
 # ---------------------------------------------------------------------------
 
-def get_file_metadata(request: HttpRequest, path: str, experiment_id: str | 
None = None) -> dict[str, Any]:
+def get_file_metadata(request: Any, path: str, experiment_id: str | None = 
None) -> dict[str, Any]:
     """Get metadata for the file at *path*. Returns a dict."""
     resp = request.airavata_client.storage.get_file_metadata(path)
     return {
@@ -243,7 +244,7 @@ def get_file_metadata(request: HttpRequest, path: str, 
experiment_id: str | None
     }
 
 
-def get_data_product_metadata(request: HttpRequest, data_product: Any = None, 
data_product_uri: str | None = None) -> dict[str, Any]:
+def get_data_product_metadata(request: Any, data_product: Any = None, 
data_product_uri: str | None = None) -> dict[str, Any]:
     """Get metadata for a data product. Returns a dict with path, size, etc."""
     if data_product is None and data_product_uri:
         data_product = 
request.airavata_client.research.get_data_product(data_product_uri)
@@ -270,14 +271,14 @@ def get_data_product_metadata(request: HttpRequest, 
data_product: Any = None, da
 # Download URL helpers
 # ---------------------------------------------------------------------------
 
-def get_download_url(request: HttpRequest, data_product_uri: str | None = 
None) -> str:
+def get_download_url(request: Any, data_product_uri: str | None = None) -> str:
     """Return a URL to download the file for *data_product_uri*."""
     from django.urls import reverse
     from urllib.parse import quote
-    return reverse("django_airavata_api:download_file") + 
f"?data-product-uri={quote(data_product_uri)}"
+    return reverse("django_airavata_api:download_file") + 
f"?data-product-uri={quote(data_product_uri or '')}"
 
 
-def get_lazy_download_url(request: HttpRequest, data_product: Any = None, 
data_product_uri: str | None = None) -> str | None:
+def get_lazy_download_url(request: Any, data_product: Any = None, 
data_product_uri: str | None = None) -> str | None:
     """Return a download URL. Accepts either a data_product or 
data_product_uri."""
     if data_product_uri:
         return get_download_url(request, data_product_uri=data_product_uri)
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 a1bc917a1..9a67c8763 100644
--- a/airavata-django-portal/django_airavata/apps/api/view_utils.py
+++ b/airavata-django-portal/django_airavata/apps/api/view_utils.py
@@ -6,7 +6,7 @@ from datetime import datetime
 from pathlib import Path
 from typing import Any
 
-import pytz
+import pytz  # ty: ignore[unresolved-import]
 from django_airavata.apps.api import user_storage
 from django.conf import settings
 from django.http import Http404
@@ -170,7 +170,7 @@ class APIResultPagination(pagination.LimitOffsetPagination):
         return super().get_limit(request)
 
     def get_paginated_response(self, data: list[Any]) -> Response:
-        has_next_link = len(data) >= self.limit
+        has_next_link = self.limit is not None and len(data) >= self.limit
         return Response(
             OrderedDict(
                 [
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py 
b/airavata-django-portal/django_airavata/apps/api/views.py
index d5e421aa8..601a3227e 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -108,8 +108,8 @@ class GroupViewSet(APIBackedViewSet):
             sharing_client.remove_group_admins(group.id, group._removed_admins)
         sharing_client.update_group(group)
 
-    def perform_destroy(self, group: Any) -> None:
-        self.request.airavata_client.sharing.delete_group(group.id, 
group.ownerId)
+    def perform_destroy(self, instance: Any) -> None:  # type: ignore[override]
+        self.request.airavata_client.sharing.delete_group(instance.id, 
instance.ownerId)
 
     def _send_users_added_to_group(self, internal_user_ids: set[str], group: 
Any) -> None:
         for internal_user_id in internal_user_ids:
@@ -1185,6 +1185,7 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin, 
mixins.UpdateModelMixin, Ge
     @action(methods=["get"], detail=True)
     def all(self, request: Request, entity_id: str | None = None) -> Response:
         """Load direct plus indirectly (inherited) shared permissions."""
+        assert entity_id is not None, "entity_id is required"
         users = {}
         # Load accessible users in order of permission precedence: users that
         # have WRITE permission should also have READ
@@ -1388,7 +1389,7 @@ class UserStoragePathView(APIView):
         experiment_id = request.query_params.get("experiment-id")
         return self._create_response(request, path, 
experiment_id=experiment_id)
 
-    def post(self, request: Request, path: str = "/", format: str | None = 
None) -> Response:
+    def post(self, request: Request, path: str = "/", format: str | None = 
None, file_name: str | None = None) -> Response:
         path = request.data.get("path", path)
         experiment_id = request.data.get("experiment-id")
         if not user_storage.dir_exists(request, path, 
experiment_id=experiment_id):
@@ -1454,7 +1455,7 @@ class UserStoragePathView(APIView):
     def _create_response(self, request: Request, path: str, uploaded: Any = 
None, experiment_id: str | None = None) -> Response:
         if user_storage.dir_exists(request, path, experiment_id=experiment_id):
             directories, files = user_storage.listdir(request, path, 
experiment_id=experiment_id)
-            data = {"isDir": True, "directories": directories, "files": files}
+            data: dict[str, Any] = {"isDir": True, "directories": directories, 
"files": files}
             if uploaded is not None:
                 data["uploaded"] = uploaded
             data["parts"] = self._split_path(path)
@@ -1463,7 +1464,7 @@ class UserStoragePathView(APIView):
             return Response(serializer.data)
         else:
             file = user_storage.get_file_metadata(request, path, 
experiment_id=experiment_id)
-            data = {"isDir": False, "directories": [], "files": [file]}
+            data: dict[str, Any] = {"isDir": False, "directories": [], 
"files": [file]}
             if uploaded is not None:
                 data["uploaded"] = uploaded
             data["parts"] = self._split_path(path)
@@ -1484,6 +1485,7 @@ class ExperimentStoragePathView(APIView):
     serializer_class = serializers.ExperimentStoragePathSerializer
 
     def get(self, request: Request, experiment_id: str | None = None, path: 
str = "", format: str | None = None) -> Response:
+        assert experiment_id is not None, "experiment_id is required"
         return self._create_response(request, experiment_id, path)
 
     def _create_response(self, request: Request, experiment_id: str, path: 
str) -> Response:
@@ -1494,7 +1496,7 @@ class ExperimentStoragePathView(APIView):
                 d["experiment_id"] = experiment_id
                 return d
 
-            data = {"isDir": True, "directories": map(add_expid, directories), 
"files": map(add_expid, files)}
+            data: dict[str, Any] = {"isDir": True, "directories": 
map(add_expid, directories), "files": map(add_expid, files)}
             data["parts"] = self._split_path(path)
             serializer = self.serializer_class(data, context={"request": 
request})
             return Response(serializer.data)
@@ -1617,6 +1619,7 @@ class IAMUserViewSet(
 
     @action(methods=["post"], detail=True)
     def enable(self, request: Request, user_id: str | None = None) -> Response:
+        assert user_id is not None, "user_id is required"
         iam_admin_client.enable_user(user_id)
         instance = self.get_instance(user_id)
         serializer = self.serializer_class(instance=instance, 
context={"request": request})
diff --git a/airavata-django-portal/django_airavata/apps/auth/backends.py 
b/airavata-django-portal/django_airavata/apps/auth/backends.py
index e9b0fc207..2230a04fb 100644
--- a/airavata-django-portal/django_airavata/apps/auth/backends.py
+++ b/airavata-django-portal/django_airavata/apps/auth/backends.py
@@ -74,7 +74,7 @@ class KeycloakBackend:
             # authz_token_middleware has already run, so must manually add
             # the `request.authz_token` attribute
             if user is not None:
-                request.authz_token = get_authz_token(request, user=user, 
access_token=access_token)
+                request.authz_token = get_authz_token(request, user=user, 
access_token=access_token)  # ty: ignore[invalid-assignment]
             return user
         except Exception as e:
             logger.warning("login failed", exc_info=e)
diff --git a/airavata-django-portal/django_airavata/apps/auth/middleware.py 
b/airavata-django-portal/django_airavata/apps/auth/middleware.py
index 195090cf3..0b702636c 100644
--- a/airavata-django-portal/django_airavata/apps/auth/middleware.py
+++ b/airavata-django-portal/django_airavata/apps/auth/middleware.py
@@ -11,6 +11,8 @@ from django.http import HttpRequest, HttpResponse
 from django.shortcuts import redirect
 from django.urls import reverse
 
+from django_airavata.types import AiravataRequest
+
 from . import utils
 
 log = logging.getLogger(__name__)
@@ -19,7 +21,7 @@ log = logging.getLogger(__name__)
 def authz_token_middleware(get_response: Callable[[HttpRequest], 
HttpResponse]) -> Callable[[HttpRequest], HttpResponse]:
     """Automatically add the 'authz_token' to the request."""
 
-    def middleware(request: HttpRequest) -> HttpResponse:
+    def middleware(request: Any) -> HttpResponse:
 
         authz_token = None
         if request.user.is_authenticated:
@@ -36,7 +38,7 @@ def authz_token_middleware(get_response: 
Callable[[HttpRequest], HttpResponse])
     return middleware
 
 
-def set_admin_group_attributes(request: HttpRequest, gateway_groups: Any = 
None) -> None:
+def set_admin_group_attributes(request: Any, gateway_groups: Any = None) -> 
None:
     """Set is_gateway_admin and is_read_only_gateway_admin request attrs."""
     if gateway_groups is None:
         gateway_groups = request.airavata_client.iam.get_gateway_groups()
@@ -58,7 +60,7 @@ def set_admin_group_attributes(request: HttpRequest, 
gateway_groups: Any = None)
 def gateway_groups_middleware(get_response: Callable[[HttpRequest], 
HttpResponse]) -> Callable[[HttpRequest], HttpResponse]:
     """Add 'is_gateway_admin' and 'is_read_only_gateway_admin' to request."""
 
-    def middleware(request: HttpRequest) -> HttpResponse:
+    def middleware(request: Any) -> HttpResponse:
 
         request.is_gateway_admin = False
         request.is_read_only_gateway_admin = False
@@ -99,7 +101,7 @@ def gateway_groups_middleware(get_response: 
Callable[[HttpRequest], HttpResponse
 def user_profile_completeness_check(get_response: Callable[[HttpRequest], 
HttpResponse]) -> Callable[[HttpRequest], HttpResponse]:
     """Check if user profile is complete and if not, redirect to user profile 
editor."""
 
-    def middleware(request: HttpRequest) -> HttpResponse:
+    def middleware(request: Any) -> HttpResponse:
 
         if not request.user.is_authenticated:
             return get_response(request)
diff --git 
a/airavata-django-portal/django_airavata/apps/auth/tests/test_backends.py 
b/airavata-django-portal/django_airavata/apps/auth/tests/test_backends.py
index 656672bdc..dbe04f6ee 100644
--- a/airavata-django-portal/django_airavata/apps/auth/tests/test_backends.py
+++ b/airavata-django-portal/django_airavata/apps/auth/tests/test_backends.py
@@ -49,7 +49,7 @@ class KeycloakBackendTestCase(TestCase):
         # Mock out request for redirect flow, and OAuth2Session: token and 
userinfo
         request = self.factory.get("/callback?code=abc123", secure=True)
         request.user = AnonymousUser()
-        request.session = {
+        request.session = {  # ty: ignore[invalid-assignment]
             "OAUTH2_STATE": "state",
             "OAUTH2_REDIRECT_URI": "redirect-uri",
         }
@@ -94,7 +94,7 @@ class KeycloakBackendTestCase(TestCase):
         # Mock out request for redirect flow, and OAuth2Session: token and 
userinfo
         request = self.factory.get("/callback?code=abc123", secure=True)
         request.user = AnonymousUser()
-        request.session = {
+        request.session = {  # ty: ignore[invalid-assignment]
             "OAUTH2_STATE": "state",
             "OAUTH2_REDIRECT_URI": "redirect-uri",
         }
@@ -141,7 +141,7 @@ class KeycloakBackendTestCase(TestCase):
         # Mock out request for redirect flow, and OAuth2Session: token and 
userinfo
         request = self.factory.get("/callback?code=abc123", secure=True)
         request.user = AnonymousUser()
-        request.session = {
+        request.session = {  # ty: ignore[invalid-assignment]
             "OAUTH2_STATE": "state",
             "OAUTH2_REDIRECT_URI": "redirect-uri",
         }
diff --git 
a/airavata-django-portal/django_airavata/apps/auth/tests/test_middleware.py 
b/airavata-django-portal/django_airavata/apps/auth/tests/test_middleware.py
index ee5ff31b9..b591b6945 100644
--- a/airavata-django-portal/django_airavata/apps/auth/tests/test_middleware.py
+++ b/airavata-django-portal/django_airavata/apps/auth/tests/test_middleware.py
@@ -13,7 +13,7 @@ from django_airavata.apps.auth.middleware import 
user_profile_completeness_check
 class UserProfileCompletenessCheckTestCase(TestCase):
     def setUp(self):
         User = get_user_model()
-        self.user: User = User.objects.create_user("testuser")
+        self.user = User.objects.create_user("testuser")
         self.user_profile: models.UserProfile = 
models.UserProfile.objects.create(user=self.user)
         self.factory = RequestFactory()
 
@@ -99,7 +99,7 @@ class UserProfileCompletenessCheckTestCase(TestCase):
         """Test user profile is complete, ext user prof is invalid, but user 
is gateway admin."""
         request = 
self.factory.get(reverse("django_airavata_workspace:dashboard"), 
HTTP_ACCEPT=["text/html"])
         request.user = self.user
-        request.is_gateway_admin = True
+        request.is_gateway_admin = True  # ty: ignore[invalid-assignment]
         self.user.first_name = "Admin"
         self.user.last_name = "User"
         self.user.email = "[email protected]"
diff --git a/airavata-django-portal/django_airavata/apps/auth/views.py 
b/airavata-django-portal/django_airavata/apps/auth/views.py
index a0d202011..a9278069f 100644
--- a/airavata-django-portal/django_airavata/apps/auth/views.py
+++ b/airavata-django-portal/django_airavata/apps/auth/views.py
@@ -209,7 +209,7 @@ def create_account(request):
                     )
                     return 
redirect(reverse("django_airavata_auth:create_account"))
             except Exception as e:
-                logger.exception("Failed to create account for user", 
exc_info=e, extra={"request", request})
+                logger.exception("Failed to create account for user", 
exc_info=e, extra={"request": request})
                 form.add_error(None, ValidationError(e.message))
     else:
         form = forms.CreateAccountForm(initial=request.GET)
diff --git a/airavata-django-portal/django_airavata/apps/groups/forms.py 
b/airavata-django-portal/django_airavata/apps/groups/forms.py
index a880ca9ba..0251720e3 100755
--- a/airavata-django-portal/django_airavata/apps/groups/forms.py
+++ b/airavata-django-portal/django_airavata/apps/groups/forms.py
@@ -41,10 +41,10 @@ class CreateForm(forms.Form):
 class AddForm(forms.Form):
     def __init__(self, data=None, user_choices=None):
         super().__init__(data=data)
-        self.fields["users"] = 
forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, 
choices=user_choices)
+        self.fields["users"] = 
forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, 
choices=user_choices or [])
 
 
 class RemoveForm(forms.Form):
     def __init__(self, data=None, user_choices=None):
         super().__init__(data=data)
-        self.fields["members"] = 
forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, 
choices=user_choices)
+        self.fields["members"] = 
forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, 
choices=user_choices or [])
diff --git a/airavata-django-portal/django_airavata/context_processors.py 
b/airavata-django-portal/django_airavata/context_processors.py
index fe0077520..7720a5a38 100644
--- a/airavata-django-portal/django_airavata/context_processors.py
+++ b/airavata-django-portal/django_airavata/context_processors.py
@@ -12,12 +12,13 @@ from django.http import HttpRequest
 from django.urls import reverse
 
 from django_airavata.app_config import AiravataAppConfig
+from django_airavata.types import AiravataRequest
 from django_airavata.apps.api.models import User_Notifications
 
 logger = logging.getLogger(__name__)
 
 
-def get_notifications(request: HttpRequest) -> dict[str, Any]:
+def get_notifications(request: AiravataRequest) -> dict[str, Any]:
     if request.user.is_authenticated and hasattr(request, "airavata_client"):
         unread_notifications = 0
         try:
diff --git a/airavata-django-portal/django_airavata/middleware.py 
b/airavata-django-portal/django_airavata/middleware.py
index c908ca184..3e145609a 100644
--- a/airavata-django-portal/django_airavata/middleware.py
+++ b/airavata-django-portal/django_airavata/middleware.py
@@ -1,5 +1,6 @@
 import logging
 from collections.abc import Callable
+from typing import Any
 
 from django.conf import settings
 from django.http import HttpRequest, HttpResponse
@@ -14,7 +15,7 @@ class AiravataClientMiddleware:
     def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> 
None:
         self.get_response = get_response
 
-    def __call__(self, request: HttpRequest) -> HttpResponse:
+    def __call__(self, request: Any) -> HttpResponse:
         access_token = _get_access_token(request)
         gateway_id = settings.GATEWAY_ID
         request.airavata_client = create_airavata_client(access_token, 
gateway_id)
@@ -45,5 +46,5 @@ class AiravataClientMiddleware:
 def _get_access_token(request: HttpRequest) -> str:
     """Extract access token from request auth or session."""
     if hasattr(request, "auth") and request.auth is not None:
-        return request.auth
+        return str(request.auth)
     return request.session.get("ACCESS_TOKEN", "")
diff --git a/airavata-django-portal/django_airavata/proto_compat.py 
b/airavata-django-portal/django_airavata/proto_compat.py
index 7f3062e98..614cd97b7 100644
--- a/airavata-django-portal/django_airavata/proto_compat.py
+++ b/airavata-django-portal/django_airavata/proto_compat.py
@@ -9,7 +9,10 @@ and read via attribute access), simple Python classes with 
__init__(**kwargs)
 suffice since the SDK's gRPC facade returns these objects already.
 """
 
+from __future__ import annotations
+
 import enum
+from typing import Any
 
 # ---------------------------------------------------------------------------
 # Enums
@@ -63,6 +66,7 @@ class ResourcePermissionType(enum.IntEnum):
     WRITE = 0
     READ = 1
     MANAGE_SHARING = 2
+    OWNER = 3
 
 
 class ResourceType(enum.IntEnum):
@@ -161,7 +165,8 @@ class SetEnvPaths(_ThriftLikeBase):
 
 
 class ApplicationInterfaceDescription(_ThriftLikeBase):
-    pass
+    applicationInputs: list[InputDataObjectType]
+    applicationOutputs: list[OutputDataObjectType]
 
 
 # -- App Catalog: compute resource --
@@ -214,7 +219,9 @@ class ComputeResourceReservation(_ThriftLikeBase):
 
 
 class GroupComputeResourcePreference(_ThriftLikeBase):
-    pass
+    resourceType: ResourceType | None
+    specificPreferences: Any
+    computeResourceId: str | None
 
 
 class GroupResourceProfile(_ThriftLikeBase):
@@ -230,7 +237,8 @@ class BatchQueueResourcePolicy(_ThriftLikeBase):
 
 
 class EnvironmentSpecificPreferences(_ThriftLikeBase):
-    pass
+    slurm: Any
+    aws: Any
 
 
 class SlurmComputeResourcePreference(_ThriftLikeBase):
@@ -315,7 +323,9 @@ class ExperimentSummaryModel(_ThriftLikeBase):
 
 
 class GroupModel(_ThriftLikeBase):
-    pass
+    name: str | None
+    id: str | None
+    ownerId: str | None
 
 
 # -- Job --
diff --git a/airavata-django-portal/django_airavata/settings.py 
b/airavata-django-portal/django_airavata/settings.py
index 0b545ec1b..d2de9a8fa 100644
--- a/airavata-django-portal/django_airavata/settings.py
+++ b/airavata-django-portal/django_airavata/settings.py
@@ -568,7 +568,7 @@ WAGTAIL_CODE_BLOCK_LANGUAGES = (
 
 # Allow all settings to be overridden by settings_local.py file
 try:
-    from django_airavata.settings_local import *  # noqa
+    from django_airavata.settings_local import *  # ty: 
ignore[unresolved-import]  # noqa
 except ImportError:
     pass
 
diff --git a/airavata-django-portal/django_airavata/types.py 
b/airavata-django-portal/django_airavata/types.py
new file mode 100644
index 000000000..592ffae0a
--- /dev/null
+++ b/airavata-django-portal/django_airavata/types.py
@@ -0,0 +1,20 @@
+"""Type definitions for the Airavata Django Portal."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from django.http import HttpRequest
+
+if TYPE_CHECKING:
+    from airavata_sdk.client import AiravataClient  # ty: 
ignore[unresolved-import]
+
+
+class AiravataRequest(HttpRequest):
+    """Extended HttpRequest with Airavata client attached by middleware."""
+
+    airavata_client: AiravataClient
+    authz_token: dict[str, Any] | None
+    is_gateway_admin: bool
+    is_read_only_gateway_admin: bool
+    active_nav_item: str
diff --git a/airavata-django-portal/django_airavata/utils.py 
b/airavata-django-portal/django_airavata/utils.py
index 23b7bac21..b5fa06695 100644
--- a/airavata-django-portal/django_airavata/utils.py
+++ b/airavata-django-portal/django_airavata/utils.py
@@ -1,6 +1,6 @@
 import logging
 
-from airavata_sdk import AiravataClient
+from airavata_sdk import AiravataClient  # ty: ignore[unresolved-import]
 from django.conf import settings
 
 log = logging.getLogger(__name__)
diff --git a/airavata-django-portal/django_airavata/wagtailapps/base/blocks.py 
b/airavata-django-portal/django_airavata/wagtailapps/base/blocks.py
index 45dbfe01c..fecf017e6 100644
--- a/airavata-django-portal/django_airavata/wagtailapps/base/blocks.py
+++ b/airavata-django-portal/django_airavata/wagtailapps/base/blocks.py
@@ -13,7 +13,7 @@ from wagtail.blocks import (
 from wagtail.documents.blocks import DocumentChooserBlock
 from wagtail.embeds.blocks import EmbedBlock
 from wagtail.images.blocks import ImageChooserBlock
-from wagtailcodeblock.blocks import CodeBlock
+from wagtailcodeblock.blocks import CodeBlock  # ty: ignore[unresolved-import]
 
 
 class ImageBlock(StructBlock):


Reply via email to