diff --git a/indra/lib/python/indra/util/iterators.py b/indra/lib/python/indra/util/iterators.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a98c97f8b74fa0e60edf28b1f3bdea6797b8635
--- /dev/null
+++ b/indra/lib/python/indra/util/iterators.py
@@ -0,0 +1,63 @@
+"""\
+@file iterators.py
+@brief Useful general-purpose iterators.
+
+$LicenseInfo:firstyear=2008&license=mit$
+
+Copyright (c) 2008, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+from __future__ import nested_scopes
+
+def iter_chunks(rows, aggregate_size=100):
+    """
+    Given an iterable set of items (@p rows), produces lists of up to @p
+    aggregate_size items at a time, for example:
+    
+    iter_chunks([1,2,3,4,5,6,7,8,9,10], 3)
+
+    Values for @p aggregate_size < 1 will raise ValueError.
+
+    Will return a generator that produces, in the following order:
+    - [1, 2, 3]
+    - [4, 5, 6]
+    - [7, 8, 9]
+    - [10]
+    """
+    if aggregate_size < 1:
+        raise ValueError()
+
+    def iter_chunks_inner():
+        row_iter = iter(rows)
+        done = False
+        agg = []
+        while not done:
+            try:
+                row = row_iter.next()
+                agg.append(row)
+            except StopIteration:
+                done = True
+            if agg and (len(agg) >= aggregate_size or done):
+                yield agg
+                agg = []
+    
+    return iter_chunks_inner()
diff --git a/indra/lib/python/indra/util/iterators_test.py b/indra/lib/python/indra/util/iterators_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..7fd9e73b357f398117071729660d586d57d62824
--- /dev/null
+++ b/indra/lib/python/indra/util/iterators_test.py
@@ -0,0 +1,72 @@
+"""\
+@file iterators_test.py
+@brief Test cases for iterators module.
+
+$LicenseInfo:firstyear=2008&license=mit$
+
+Copyright (c) 2008, Linden Research, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+$/LicenseInfo$
+"""
+
+import unittest
+
+from indra.util.iterators import iter_chunks
+
+class TestIterChunks(unittest.TestCase):
+    """Unittests for iter_chunks"""
+    def test_bad_agg_size(self):
+        rows = [1,2,3,4]
+        self.assertRaises(ValueError, iter_chunks, rows, 0)
+        self.assertRaises(ValueError, iter_chunks, rows, -1)
+
+        try:
+            for i in iter_chunks(rows, 0):
+                pass
+        except ValueError:
+            pass
+        else:
+            self.fail()
+        
+        try:
+            result = list(iter_chunks(rows, 0))
+        except ValueError:
+            pass
+        else:
+            self.fail()
+    def test_empty(self):
+        rows = []
+        result = list(iter_chunks(rows))
+        self.assertEqual(result, [])
+    def test_small(self):
+        rows = [[1]]
+        result = list(iter_chunks(rows, 2))
+        self.assertEqual(result, [[[1]]])
+    def test_size(self):
+        rows = [[1],[2]]
+        result = list(iter_chunks(rows, 2))
+        self.assertEqual(result, [[[1],[2]]])
+    def test_multi_agg(self):
+        rows = [[1],[2],[3],[4],[5]]
+        result = list(iter_chunks(rows, 2))
+        self.assertEqual(result, [[[1],[2]],[[3],[4]],[[5]]])
+
+if __name__ == "__main__":
+    unittest.main()