Skip to content
22 changes: 22 additions & 0 deletions src/zos_files/zowe/zos_files_for_zowe_sdk/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

_ZOWE_FILES_DEFAULT_ENCODING = zos_file_constants["ZoweFilesDefaultEncoding"]

_MIN_TIMEOUT = 5
_MAX_TIMEOUT = 600

class Files(SdkApi): # type: ignore
"""
Expand Down Expand Up @@ -55,6 +57,26 @@ class Files(SdkApi): # type: ignore
def __init__(self, connection: dict[str, Any], log: bool = True):
super().__init__(connection, "/zosmf/restfiles/", logger_name=__name__, log=log)
self._default_headers["Accept-Encoding"] = "gzip"

async_threshold = connection.get("asyncThreshold") or connection.get("X-IBM-Async-Threshold")
if not async_threshold:
resp_to = connection.get("responseTimeout")
if resp_to is not None:
try:
resp_to_int = int(resp_to)
clamped = max(_MIN_TIMEOUT, min(_MAX_TIMEOUT, resp_to_int))
if clamped != resp_to_int and hasattr(self, "logger"):
self.logger.warning(
f"responseTimeout {resp_to_int} out of range; clamped to {clamped} (allowed {_MIN_TIMEOUT}-{_MAX_TIMEOUT})"
)
self._default_headers["X-IBM-Response-Timeout"] = str(clamped)
except (TypeError, ValueError):
if hasattr(self, "logger"):
self.logger.warning("responseTimeout must be an integer between 5 and 600; header not set")
else:
if hasattr(self, "logger"):
self.logger.info("X-IBM-Async-Threshold present; X-IBM-Response-Timeout will be ignored by z/OSMF")

self.ds = Datasets(connection)
self.uss = USSFiles(connection)
self.fs = FileSystems(connection)
Expand Down
4 changes: 4 additions & 0 deletions src/zosmf/zowe/zosmf_for_zowe_sdk/zosmf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ class Zosmf(SdkApi): # type: ignore
def __init__(self, connection: dict[str, Any], log: bool = True):
super().__init__(connection, "/zosmf/info", logger_name=__name__, log=log)

response_timeout = connection.get("responseTimeout")
if response_timeout:
self._default_headers["X-IBM-Response-Timeout"] = response_timeout

def get_info(self) -> ZosmfResponse:
"""
Return a JSON response from the GET request to z/OSMF info endpoint.
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_zosmf.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,26 @@ def test_list_systems(self, mock_send_request):

Zosmf(self.connection_dict).list_systems()
mock_send_request.assert_called_once()

@mock.patch("requests.Session.send")
def test_response_timeout_header_added(self, mock_send_request):
"""Response timeout should add the X-IBM-Response-Timeout header to requests."""
connection_with_timeout = self.connection_dict.copy()
connection_with_timeout["responseTimeout"] = "5"

mock_response = mock.Mock()
mock_response.headers = {"Content-Type": "application/json"}
mock_response.status_code = 200
mock_response.json.return_value = {}
mock_send_request.return_value = mock_response

# Call a method that triggers a request
Zosmf(connection_with_timeout).list_systems()

# Verify the request was called and inspect the headers
mock_send_request.assert_called_once()
# Get the PreparedRequest object from the call args
prepared_request = mock_send_request.call_args[0][0]
self.assertIn("X-IBM-Response-Timeout", prepared_request.headers)
self.assertEqual(prepared_request.headers["X-IBM-Response-Timeout"], "5")