diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index f0d148fbeb8..3155868686c 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: go-version: ['1.22.x'] - os: [ubuntu-latest] + os: [ubuntu-latest, macos-latest] python-version: ['3.8','3.9','3.10','3.11'] runs-on: ${{ matrix.os }} steps: diff --git a/python/aistore/sdk/job.py b/python/aistore/sdk/job.py index a85dc63c2d8..00d51bc1b9b 100644 --- a/python/aistore/sdk/job.py +++ b/python/aistore/sdk/job.py @@ -236,41 +236,43 @@ def start( ) return resp.text + def _parse_iso_datetime(self, dt_str): + # Remove 'Z' timezone designator if present + if dt_str.endswith("Z"): + dt_str = dt_str[:-1] + # Handle fractional seconds (nanoseconds) by truncating to microseconds + if "." in dt_str: + date_part, frac_part = dt_str.split(".") + # Truncate or pad the fractional part to 6 digits (microseconds) + frac_part = (frac_part + "000000")[:6] + dt_str = f"{date_part}.{frac_part}" + return datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S.%f") + return datetime.strptime(dt_str, "%Y-%m-%dT%H:%M:%S") + def get_within_timeframe( - self, start_time: datetime.time, end_time: datetime.time + self, start_time: datetime.datetime, end_time: datetime.datetime ) -> List[JobSnapshot]: """ - Checks for jobs that started and finished within a specified timeframe + Checks for jobs that started and finished within a specified timeframe. Args: - start_time (datetime.time): The start of the timeframe for monitoring jobs - end_time (datetime.time): The end of the timeframe for monitoring jobs + start_time (datetime.datetime): The start of the timeframe for monitoring jobs. + end_time (datetime.datetime): The end of the timeframe for monitoring jobs. Returns: - list: A list of jobs that have finished within the specified timeframe + List[JobSnapshot]: A list of jobs that have finished within the specified timeframe. Raises: - requests.RequestException: "There was an ambiguous exception that occurred while handling..." - requests.ConnectionError: Connection error - requests.ConnectionTimeout: Timed out connecting to AIStore - requests.ReadTimeout: Timed out waiting response from AIStore - errors.Timeout: Timeout while waiting for the job to finish - errors.JobInfoNotFound: Raised when information on a job's status could not be found on the AIS cluster + JobInfoNotFound: Raised when information on a job's status could not be found. """ - snapshots = self._query_job_snapshots() jobs_found = [] for snapshot in snapshots: if snapshot.id == self.job_id or snapshot.kind == self.job_kind: - snapshot_start_time = datetime.fromisoformat( - snapshot.start_time[:26] - ).time() - snapshot_end_time = datetime.fromisoformat( - snapshot.end_time[:26] - ).time() + snapshot_start_time = self._parse_iso_datetime(snapshot.start_time) + snapshot_end_time = self._parse_iso_datetime(snapshot.end_time) if snapshot_start_time >= start_time and snapshot_end_time <= end_time: jobs_found.append(snapshot) - print(snapshot) if len(jobs_found) == 0: raise JobInfoNotFound("No relevant job info found") return jobs_found diff --git a/python/tests/integration/sdk/test_job_ops.py b/python/tests/integration/sdk/test_job_ops.py index f63e8baac6b..0b05b872a7a 100644 --- a/python/tests/integration/sdk/test_job_ops.py +++ b/python/tests/integration/sdk/test_job_ops.py @@ -72,10 +72,10 @@ def test_job_wait_single_node(self): self._validate_objects_cached(objects, True) def test_get_within_timeframe(self): - start_time = datetime.now().time() + start_time = datetime.now() job_id = self.client.job(job_kind="lru").start() self.client.job(job_id=job_id).wait() - end_time = datetime.now().time() + end_time = datetime.now() self.assertNotEqual(job_id, "") jobs_list = self.client.job(job_id=job_id).get_within_timeframe( start_time=start_time, end_time=end_time diff --git a/python/tests/integration/sdk/test_object_group_ops.py b/python/tests/integration/sdk/test_object_group_ops.py index cb1160716f9..ca29d0bf6ce 100644 --- a/python/tests/integration/sdk/test_object_group_ops.py +++ b/python/tests/integration/sdk/test_object_group_ops.py @@ -91,10 +91,10 @@ def test_prefetch_blob_download(self): self.obj_names.extend(obj_names) obj_group = self.bucket.objects(obj_names=obj_names) self._evict_all_objects(num_obj=OBJECT_COUNT + 1) - start_time = datetime.now().time() + start_time = datetime.now() job_id = obj_group.prefetch(blob_threshold=2 * MIB) self.client.job(job_id=job_id).wait(timeout=TEST_TIMEOUT * 2) - end_time = datetime.now().time() + end_time = datetime.now() jobs_list = self.client.job(job_kind="blob-download").get_within_timeframe( start_time=start_time, end_time=end_time ) @@ -113,10 +113,10 @@ def test_prefetch_without_blob_download(self): self.obj_names.extend(obj_names) obj_group = self.bucket.objects(obj_names=obj_names) self._evict_all_objects(num_obj=OBJECT_COUNT + 1) - start_time = datetime.now().time() + start_time = datetime.now() job_id = obj_group.prefetch(blob_threshold=2 * SMALL_FILE_SIZE) self.client.job(job_id=job_id).wait(timeout=TEST_TIMEOUT * 2) - end_time = datetime.now().time() + end_time = datetime.now() with self.assertRaises(JobInfoNotFound): self.client.job(job_kind="blob-download").get_within_timeframe( diff --git a/python/tests/integration/sdk/test_object_ops.py b/python/tests/integration/sdk/test_object_ops.py index 362b5b79f20..d93d6008887 100644 --- a/python/tests/integration/sdk/test_object_ops.py +++ b/python/tests/integration/sdk/test_object_ops.py @@ -206,7 +206,7 @@ def test_get_blob_download(self, testcase): self.client.job(evict_job_id).wait(timeout=TEST_TIMEOUT) for obj_name, content in objects.items(): - start_time = datetime.now().time() + start_time = datetime.now() blob_download_settings = BlobDownloadSettings( chunk_size=testcase, num_workers="4" ) @@ -216,7 +216,7 @@ def test_get_blob_download(self, testcase): .read_all() ) self.assertEqual(content, resp) - end_time = datetime.now().time() + end_time = datetime.now() jobs_list = self.client.job(job_kind="blob-download").get_within_timeframe( start_time=start_time, end_time=end_time ) diff --git a/python/tests/unit/sdk/test_job.py b/python/tests/unit/sdk/test_job.py index 5db63b1b6cd..e45687a17cb 100644 --- a/python/tests/unit/sdk/test_job.py +++ b/python/tests/unit/sdk/test_job.py @@ -312,7 +312,7 @@ def test_get_within_timeframe_found_jobs(self): self.mock_client.request_deserialize.return_value = {"key": mock_snapshots} - found_jobs = self.job.get_within_timeframe(start_time.time(), end_time.time()) + found_jobs = self.job.get_within_timeframe(start_time, end_time) self.assertEqual(len(found_jobs), len(mock_snapshots)) for found_job, expected_snapshot in zip(found_jobs, mock_snapshots): @@ -326,4 +326,4 @@ def test_get_within_timeframe_no_jobs_found(self): self.mock_client.request_deserialize.return_value = {} with self.assertRaises(JobInfoNotFound): - self.job.get_within_timeframe(start_time.time(), end_time.time()) + self.job.get_within_timeframe(start_time, end_time)