Repository: ASFHyP3/hyp3-sdk

Issue:
Batch should be iterable
Attempting to iterate over a Batch object currently fails with `TypeError: 'Batch' object is not iterable`.

```
> import hyp3_sdk
> api = hyp3_sdk.HyP3()
> jobs = api.find_jobs(name='refactor')
> sizes = [job['files'][0]['size'] for job in jobs]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Batch' object is not iterable
```

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 620eb3f..38529ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
   - `HyP3.prepare_insar_job`
     
 ### Changed
+- HyP3 `Batch` objects are now iterable
 - HyP3 submit methods will always return a `Batch` containing the submitted job(s)
 - `HyP3.submit_job_dict` has been renamed to `HyP3.submit_prepared_jobs` and can
   submit one or more prepared job dictionaries.
diff --git a/hyp3_sdk/jobs.py b/hyp3_sdk/jobs.py
index 9167d02..38054fa 100644
--- a/hyp3_sdk/jobs.py
+++ b/hyp3_sdk/jobs.py
@@ -129,9 +129,6 @@ class Batch:
             jobs = []
         self.jobs = jobs
 
-    def __len__(self):
-        return len(self.jobs)
-
     def __add__(self, other: Union[Job, 'Batch']):
         if isinstance(other, Batch):
             return Batch(self.jobs + other.jobs)
@@ -140,6 +137,12 @@ class Batch:
         else:
             raise TypeError(f"unsupported operand type(s) for +: '{type(self)}' and '{type(other)}'")
 
+    def __iter__(self):
+        return iter(self.jobs)
+
+    def __len__(self):
+        return len(self.jobs)
+
     def __repr__(self):
         return str([job.to_dict() for job in self.jobs])
 


Test patch:
diff --git a/tests/test_jobs.py b/tests/test_jobs.py
index 400f2d8..dab034b 100644
--- a/tests/test_jobs.py
+++ b/tests/test_jobs.py
@@ -118,17 +118,6 @@ def test_job_download_files(tmp_path, get_mock_job):
     assert contents == 'foobar2'
 
 
-def test_batch_len():
-    batch = Batch()
-    assert len(batch) == 0
-
-    batch = Batch([])
-    assert len(batch) == 0
-
-    batch = Batch([Job.from_dict(SUCCEEDED_JOB), Job.from_dict(FAILED_JOB)])
-    assert len(batch) == 2
-
-
 def test_batch_add():
     a = Batch([Job.from_dict(SUCCEEDED_JOB)])
     b = Batch([Job.from_dict(FAILED_JOB)])
@@ -147,6 +136,24 @@ def test_batch_add():
     assert d.jobs[2].running()
 
 
+def test_batch_iter():
+    defined_jobs = [Job.from_dict(SUCCEEDED_JOB), Job.from_dict(FAILED_JOB)]
+    batch = Batch(defined_jobs)
+    for batch_job, defined_job in zip(batch, defined_jobs):
+        assert batch_job == defined_job
+
+
+def test_batch_len():
+    batch = Batch()
+    assert len(batch) == 0
+
+    batch = Batch([])
+    assert len(batch) == 0
+
+    batch = Batch([Job.from_dict(SUCCEEDED_JOB), Job.from_dict(FAILED_JOB)])
+    assert len(batch) == 2
+
+
 def test_batch_complete_succeeded():
     batch = Batch([Job.from_dict(SUCCEEDED_JOB), Job.from_dict(SUCCEEDED_JOB)])
     assert batch.complete()