Index: geos/base.py
===================================================================
--- geos/base.py	(revision 2)
+++ geos/base.py	(revision 13)
@@ -594,6 +594,180 @@
         "Clones this Geometry."
         return GEOSGeometry(geom_clone(self.ptr), srid=self.srid)
 
+class ListMixin(object):
+
+    def __init__(self, *args, **kwargs):
+        if not hasattr(self, '_getItemInternal'):
+            self._getItemInternal = self._getItemExternal
+
+        if hasattr(self, '_setSingle'):
+            self._canSetSingle = True
+            self._assignExtendedSlice = self._assignExtendedSlice_no_rebuild
+        else:
+            self._canSetSingle = False
+            self._setSingle = self._setSingle_rebuild
+
+        super(ListMixin, self).__init__(*args, **kwargs)
+
+    def _setSingle_rebuild(self, index, value):
+        self._checkindex(index)
+        self._setSlice(slice(index, index + 1, 1), [value])        
+
+    def _checkindex(self, index):
+        if index < 0 or index >= len(self):
+            raise IndexError('invalid index: %s' % str(index))
+
+    def _checkAllowedTypes(self, items):
+        # Ensuring that only the permitted geometries are allowed in this collection
+        if hasattr(self, '_allowed'):
+            if False in [isinstance(geom, self._allowed) for geom in items]:
+                raise TypeError('Invalid Geometry type encountered in the arguments.')
+
+    def __getitem__(self, index):
+        "Gets the coordinates of the point(s) at the specified index/slice."
+        if isinstance(index, slice):
+            return [self._getItemExternal(i) for i in xrange(*index.indices(len(self)))]
+        else:
+            if index < 0:
+                index += len(self)
+            return self._getItemExternal(index)
+
+    def __delitem__(self, index):
+        "Delete the point(s) at the specified index/slice."
+        if not isinstance(index, (int, long, slice)):
+            raise TypeError("%s is not a legal index" % index)
+
+        # calculate new length and dimensions
+        origLen     = len(self)
+        if isinstance(index, (int, long)):
+            if index < 0: index += origLen
+            if not 0 <= index < origLen:
+                raise IndexError('invalid index: %d' % index)
+            indexRange  = [index]
+        else:
+            indexRange  = range(*index.indices(origLen))
+
+        newLen      = origLen - len(indexRange)
+        newItems    = ( self._getItemInternal(i)
+                        for i in xrange(origLen)
+                        if i not in indexRange )
+
+        self._setCollection(newLen, newItems)
+
+    def __setitem__(self, index, geom):
+        "Sets the Geometry at the specified index."
+        if isinstance(index, slice):
+            self._setSlice(index, geom)
+        else:
+            if index < 0: index += len(self)
+            self._setSingle(index, geom)
+
+    def _setSlice(self, index, values):
+        "Assign values to a slice of the object"
+        try:
+            iter(values)
+        except TypeError:
+            raise TypeError('can only assign an iterable to a slice')
+
+        self._checkAllowedTypes(values)
+
+        origLen     = len(self)
+        valueList   = list(values)
+        start, stop, step = index.indices(origLen)
+        stop = max(0, stop) # stop will be -1 if out-of-bounds
+                            # negative index is given
+
+        # CAREFUL: index.step and step are not the same!
+        # step will never be None
+        #
+        if index.step is None:
+            self._assignSimpleSlice(start, stop, valueList)
+        else:
+            self._assignExtendedSlice(start, stop, step, valueList)
+
+    def _assignExtendedSlice(self, start, stop, step, valueList):
+        'Assign an extended slice by rebuilding entire list'
+        indexList   = range(start, stop, step)
+        # extended slice, only allow assigning slice of same size
+        if len(valueList) != len(indexList):
+            raise ValueError('attempt to assign sequence of size %d '
+                             'to extended slice of size %d'
+                             % (len(valueList), len(indexList)))
+
+        # we're not changing the length of the sequence
+        newLen  = len(self)
+        newVals = dict(zip(indexList, valueList))
+        def newItems():
+            for i in xrange(newLen):
+                if i in newVals:
+                    yield newVals[i]
+                else:
+                    yield self._getItemInternal(i)
+
+        self._setCollection(newLen, newItems())
+
+    def _assignExtendedSlice_no_rebuild(self, start, stop, step, valueList):
+        'Assign an extended slice by re-assigning individual items'
+        indexList   = range(start, stop, step)
+        # extended slice, only allow assigning slice of same size
+        if len(valueList) != len(indexList):
+            raise ValueError('attempt to assign sequence of size %d '
+                             'to extended slice of size %d'
+                             % (len(valueList), len(indexList)))
+
+        for i, val in zip(indexList, valueList):
+            self._setSingle(i, val)
+
+    def _assignSimpleSlice(self, start, stop, valueList):
+        'Assign a simple slice; Can assign slice of any length'
+        origLen = len(self)
+        newLen  = origLen - stop + start + len(valueList)
+        def newItems():
+            for i in xrange(origLen + 1):
+                if i == start:
+                    for val in valueList:
+                        yield val
+
+                if i < origLen:
+                    if i < start or i >= stop:
+                        yield self._getItemInternal(i)
+
+        self._setCollection(newLen, newItems())
+
+    def append(self, val):
+        "Standard list append method"
+        self[len(self):] = [val]
+
+    def extend(self, vals):
+        "Standard list extend method"
+        self[len(self):] = vals
+
+    def insert(self, index, val):
+        "Standard list insert method"
+        if not isinstance(index, (int, long)):
+            raise TypeError("%s is not a legal index" % index)
+        self[index:index] = [val]
+
+    def pop(self, index=-1):
+        "Standard list pop method"
+        result = self[index]
+        del self[index]
+        return result
+
+    def index(self, val):
+        "Standard list index method"
+        for i in xrange(0, len(self)):
+            if self[i] == val: return i
+        raise ValueError('%s not in geometry' % str(val))
+
+    def remove(self, val):
+        "Standard list remove method"
+        del self[self.index(val)]
+
+    def count(self):
+        "Standard list count method"
+        return len(self)
+
 # Class mapping dictionary
 from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
 from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
@@ -606,3 +780,4 @@
                 6 : MultiPolygon,
                 7 : GeometryCollection,
                 }
+
Index: geos/collections.py
===================================================================
--- geos/collections.py	(revision 2)
+++ geos/collections.py	(revision 13)
@@ -4,16 +4,30 @@
 """
 from ctypes import c_int, c_uint, byref
 from types import TupleType, ListType
-from django.contrib.gis.geos.base import GEOSGeometry
+from django.contrib.gis.geos.base import GEOSGeometry, ListMixin
 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
 from django.contrib.gis.geos.geometries import Point, LineString, LinearRing, Polygon
 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
 from django.contrib.gis.geos.prototypes import create_collection, destroy_geom, geom_clone, geos_typeid, get_cs, get_geomn
 
-class GeometryCollection(GEOSGeometry):
+class GeometryCollection(ListMixin, GEOSGeometry):
     _allowed = (Point, LineString, LinearRing, Polygon)
     _typeid = 7
 
+    @classmethod
+    def _createCollection(cls, length, items):
+        # Creating the geometry pointer array.
+        geoms = get_pointer_arr(length)
+        for i, g in enumerate(items):
+            # this is a little sloppy, but makes life easier
+            # allow GEOSGeometry types (python wrappers) or pointer types
+            if hasattr(g, 'ptr'):
+                geoms[i] = geom_clone(g.ptr)
+            else:
+                geoms[i] = geom_clone(g)
+
+        return create_collection(c_int(cls._typeid), byref(geoms), c_uint(length))
+
     def __init__(self, *args, **kwargs):
         "Initializes a Geometry Collection from a sequence of Geometry objects."
 
@@ -32,39 +46,26 @@
             init_geoms = args
 
         # Ensuring that only the permitted geometries are allowed in this collection
-        if False in [isinstance(geom, self._allowed) for geom in init_geoms]:
-            raise TypeError('Invalid Geometry type encountered in the arguments.')
+        self._checkAllowedTypes(init_geoms)
 
         # Creating the geometry pointer array.
-        ngeoms = len(init_geoms)
-        geoms = get_pointer_arr(ngeoms)
-        for i in xrange(ngeoms): geoms[i] = geom_clone(init_geoms[i].ptr)
-        super(GeometryCollection, self).__init__(create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms)), **kwargs)
+        collection = self._createCollection(len(init_geoms), iter(init_geoms))
+        super(GeometryCollection, self).__init__(collection, **kwargs)
+        
+    def _getItemInternal(self, index):
+        return get_geomn(self.ptr, index)
 
-    def __getitem__(self, index):
+    def _getItemExternal(self, index):
         "Returns the Geometry from this Collection at the given index (0-based)."
         # Checking the index and returning the corresponding GEOS geometry.
         self._checkindex(index)
-        return GEOSGeometry(geom_clone(get_geomn(self.ptr, index)), srid=self.srid)
+        return GEOSGeometry(geom_clone(self._getItemInternal(index)), srid=self.srid)
 
-    def __setitem__(self, index, geom):
-        "Sets the Geometry at the specified index."
-        self._checkindex(index)
-        if not isinstance(geom, self._allowed):
-            raise TypeError('Incompatible Geometry for collection.')
-        
-        ngeoms = len(self)
-        geoms = get_pointer_arr(ngeoms)
-        for i in xrange(ngeoms):
-            if i == index:
-                geoms[i] = geom_clone(geom.ptr)
-            else:
-                geoms[i] = geom_clone(get_geomn(self.ptr, i))
-        
-        # Creating a new collection, and destroying the contents of the previous poiner.
+    def _setCollection(self, length, items):
+        "Create a new collection, and destroy the contents of the previous pointer."
         prev_ptr = self.ptr
         srid = self.srid
-        self._ptr = create_collection(c_int(self._typeid), byref(geoms), c_uint(ngeoms))
+        self._ptr = self._createCollection(length, items)
         if srid: self.srid = srid
         destroy_geom(prev_ptr)
 
@@ -77,11 +78,6 @@
         "Returns the number of geometries in this Collection."
         return self.num_geom
 
-    def _checkindex(self, index):
-        "Checks the given geometry index."
-        if index < 0 or index >= self.num_geom:
-            raise GEOSIndexError('invalid GEOS Geometry index: %s' % str(index))
-
     @property
     def kml(self):
         "Returns the KML for this Geometry Collection."
Index: geos/geometries.py
===================================================================
--- geos/geometries.py	(revision 2)
+++ geos/geometries.py	(revision 13)
@@ -4,7 +4,7 @@
  GEOSGeometry.
 """
 from ctypes import c_uint, byref
-from django.contrib.gis.geos.base import GEOSGeometry
+from django.contrib.gis.geos.base import GEOSGeometry, ListMixin
 from django.contrib.gis.geos.coordseq import GEOSCoordSeq
 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY
@@ -104,7 +104,7 @@
     tuple = property(get_coords, set_coords)
     coords = tuple
 
-class LineString(GEOSGeometry):
+class LineString(ListMixin, GEOSGeometry):
 
     #### Python 'magic' routines ####
     def __init__(self, *args, **kwargs):
@@ -156,10 +156,12 @@
 
         # Getting the correct initialization function
         if kwargs.get('ring', False):
-            func = create_linearring
+            self._init_func = create_linearring
         else:
-            func = create_linestring
+            self._init_func = create_linestring
 
+        func = self._init_func
+
         # If SRID was passed in with the keyword arguments
         srid = kwargs.get('srid', None)
        
@@ -167,12 +169,30 @@
         #  from the function.
         super(LineString, self).__init__(func(cs.ptr), srid=srid)
 
-    def __getitem__(self, index):
-        "Gets the point at the specified index."
+    def _getItemExternal(self, index):
+        self._checkindex(index)
         return self._cs[index]
 
-    def __setitem__(self, index, value):
-        "Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
+    def _setCollection(self, length, items):
+        ndim        = self._cs.dims #
+        hasz        = self._cs.hasz # I don't understand why these are different
+
+        # create a new coordinate sequence and populate accordingly
+        cs = GEOSCoordSeq(create_cs(length, ndim), z=hasz)
+        for i, c in enumerate(items):
+            cs[i] = c
+
+        ptr = self._init_func(cs.ptr)
+        if ptr:
+            destroy_geom(self.ptr)
+            self._ptr = ptr
+            self._post_init(self.srid)
+        else:
+            # can this happen?
+            raise GEOSException('Geometry resulting from slice deletion was invalid.')
+        
+    def _setSingle(self, index, value):
+        self._checkindex(index)
         self._cs[index] = value
 
     def __iter__(self):
Index: tests/__init__.py
===================================================================
--- tests/__init__.py	(revision 2)
+++ tests/__init__.py	(revision 13)
@@ -18,6 +18,7 @@
     # Tests that do not require setting up and tearing down a spatial database.
     test_suite_names = [
         'test_geos',
+        'test_geos_pymutable',
         'test_measure',
         ]
     if HAS_GDAL:
Index: tests/test_geos_pymutable.py
===================================================================
--- tests/test_geos_pymutable.py	(revision 0)
+++ tests/test_geos_pymutable.py	(revision 13)
@@ -0,0 +1,206 @@
+import unittest
+from pymutable_geometries import *
+from django.contrib.gis.geos.error import GEOSIndexError
+    
+class GEOSPyMutableTest(unittest.TestCase):
+    '''
+    Tests Pythonic Mutability of Python GEOS geometry wrappers
+    get/set/delitem on a slice, normal list methods
+    '''
+
+    def test01_getitem(self):
+        'Test getting a single item from a geometry'
+        for g in slice_geometries():
+            for i in seqrange():
+                self.assertEqual(g.coords[i], getcoords(g.geom[i]))
+
+    def test02_getslice(self):
+        'Test getting a slice from a geometry'
+        for f in getslice_functions():
+            for g in slice_geometries():
+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
+                self.assertEqual(f(g.coords), getcoords(f(g.geom)), msg)
+
+    def test03_getitem_indexException(self):
+        'Test get single item with out-of-bounds index'
+        for g in slice_geometries():
+            for i in SEQ_OUT_OF_BOUNDS:
+                self.assertRaises(IndexError, lambda: g.geom[i])
+
+    def test04_delitem_single(self):
+        'Test delete single item from a geometry'
+        for i in seqrange():
+            for g in slice_geometries():
+                if g.geom.ring and i in SEQ_BOUNDS: continue
+                del g.coords[i]
+                del g.geom[i]
+                self.assertEqual(g.tuple_coords, g.geom.coords)
+
+    def test05_delitem_slice(self):
+        'Test delete slice from a geometry'
+        for f in delslice_functions():
+            for g in slice_geometries():
+                if g.geom.ring and not f.ring: continue
+                f(g.coords)
+                f(g.geom)
+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
+                self.assertEqual(g.tuple_coords, g.geom.coords, msg)
+
+    def test06_delitem_single_indexException(self):
+        'Test delete single item with out-of-bounds index'
+        def func(x, i): del x[i]
+        for g in slice_geometries():
+            for i in SEQ_OUT_OF_BOUNDS:
+                self.assertRaises(IndexError, func, g.geom, i)
+
+    def test07_setitem_single(self):
+        "Test set single item (make sure we didn't break this)"
+        for i in seqrange():
+            for g in slice_geometries():
+                if g.geom.ring and i in SEQ_BOUNDS: continue
+                ag, ac = g.newitem(rng=(400,410))
+                g.coords[i] = ac
+                g.geom[i] = ag
+                self.assertEqual(g.tuple_coords, g.geom.coords)
+
+    def test08_setslice_simple(self):
+        'Test setting a simple slice of a geometry'
+        for g in slice_geometries():
+            for f in setslice_simple_functions(g):
+                if g.geom.ring and not f.ring: continue
+                f(g.coords)
+                f(g.geom)
+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
+                self.assertEqual(g.tuple_coords, g.geom.coords, msg)
+
+    def test09_setslice_extended(self):
+        'Test setting an extended slice of a geometry'
+        for g in slice_geometries():
+            for f in setslice_extended_functions(g):
+                if g.geom.ring and not f.ring: continue
+                f(g.coords)
+                f(g.geom)
+                msg = "fcn %s, geom %s" % (f.__name__, str(g.geom_type))
+                self.assertEqual(g.tuple_coords, g.geom.coords, msg)
+                
+    def test10_setslice_extended_mismatched(self):
+        'Test setting extended slice with array of mismatched length'
+        for g in slice_geometries():
+            ag, ac = g.newitem(rng=(400,410))
+            def func(): g.geom[2:8:2] = [ ag, ]
+            self.assertRaises(ValueError, func)
+
+    def test11_setitem_single_indexException(self):
+        'Test set single item with out-of-bounds index'
+        for g in slice_geometries():
+            ag, ac = g.newitem(rng=(400,410))
+            def func(i): g.geom[i] = ag
+            for i in SEQ_OUT_OF_BOUNDS:
+                self.assertRaises(IndexError, func, i)
+
+    def test12_append(self):
+        'Test list method append'
+        for g in slice_geometries(ring=False):
+            ag, ac = g.newitem(rng=(400,410))
+            g.geom.append(ag)
+            g.coords.append(ac)
+            self.assertEqual(g.tuple_coords, g.geom.coords)
+
+    def test13_extend(self):
+        'Test list method extend'
+        for g in slice_geometries():
+            items = g.coords_fcn(5)
+            if g.geom.ring: items[-1] = g.coords[0]
+            g.geom.extend(map(g.subtype,items))
+            g.coords.extend(items)
+            self.assertEqual(g.tuple_coords, g.geom.coords)
+
+    def test14_insert(self):
+        'Test list method insert'
+        for i in xrange(*SEQ_OUT_OF_BOUNDS):
+            for g in slice_geometries():
+                if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
+                    continue
+                ag, ac = g.newitem(rng=(200,250))
+                g.geom.insert(i, ag)
+                g.coords.insert(i, ac)
+                self.assertEqual(g.tuple_coords, g.geom.coords)
+
+    def test15_insert_typeError(self):
+        'Test list method insert raises error on invalid index'
+        for g in slice_geometries():
+            ag, ac = g.newitem(rng=(200,250))
+            self.assertRaises(TypeError, g.geom.insert, 'hi', ag)
+
+    def test16_pop(self):
+        'Test list method pop'
+        for i in seqrange():
+            for g in slice_geometries():
+                if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
+                    continue
+                self.assertEqual(g.coords.pop(i), getcoords(g.geom.pop(i)))
+
+    def test16_index(self):
+        'Test list method index'
+        for i in xrange(0, SEQ_LENGTH):
+            for g in slice_geometries():
+                if g.geom.ring and i in SEQ_BOUNDS: continue
+                ag, ac = g.newitem(rng=(400,410))
+                g.geom[i] = ag
+                self.assertEqual(i, g.geom.index(ag))
+
+    def test17_index_ValueError(self):
+        'Test list method raises ValueError if value not found'
+        for g in slice_geometries():
+            ag, ac = g.newitem(rng=(400,410))
+            self.assertRaises(ValueError, g.geom.index, ag)
+
+    def test18_remove(self):
+        'Test list method remove'
+        for i in xrange(0, SEQ_LENGTH):
+            for g in slice_geometries():
+                if g.geom.ring and i in SEQ_BOUNDS: continue
+                ag, ac = g.newitem(rng=(400,410))
+                g.geom[i] = ag
+                g.coords[i] = ac
+                g.geom.remove(ag)
+                g.coords.remove(ac)
+                self.assertEqual(g.tuple_coords, g.geom.coords)
+
+    def test19_count(self):
+        'Test list method count'
+        for g in slice_geometries():
+            self.assertEqual(SEQ_LENGTH, g.geom.count())
+
+    def test20_setslice_geos_fcns(self):
+        'Test geos properties after setting a simple slice of a geometry'
+        for g in slice_geometries(ring=False):
+            for f in setslice_simple_functions(g):
+                f(g.coords)
+                f(g.geom)
+                if not len(g.coords): continue
+                for tf in test_geos_functions():
+                    cg = g.make_geom()
+                    self.assertEqual(tf(cg) , tf(g.geom))
+
+    def test21_delslice_geos_fcns(self):
+        'Test geos properties after deleting a slice of a geometry'
+        for f in delslice_functions():
+            for g in slice_geometries(ring=False):
+                f(g.coords)
+                f(g.geom)
+                if not len(g.coords): continue
+                for tf in test_geos_functions():
+                    cg = g.make_geom()
+                    self.assertEqual(tf(cg) , tf(g.geom))
+
+def suite():
+    s = unittest.TestSuite()
+    s.addTest(unittest.makeSuite(GEOSPyMutableTest))
+    return s
+
+def run(verbosity=2):
+    unittest.TextTestRunner(verbosity=verbosity).run(suite())
+
+if __name__ == '__main__':
+    run()

Property changes on: tests/test_geos_pymutable.py
___________________________________________________________________
Name: svn:executable
   + *

Index: tests/pymutable_geometries.py
===================================================================
--- tests/pymutable_geometries.py	(revision 0)
+++ tests/pymutable_geometries.py	(revision 13)
@@ -0,0 +1,185 @@
+from django.contrib.gis.geos import *
+from random import random
+
+SEQ_LENGTH = 10
+SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH)
+SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1)
+SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH)
+
+def seqrange(): return xrange(*SEQ_RANGE)
+
+def random_coord(dim    = 2,           # coordinate dimensions
+                 rng    = (-50,50),    # coordinate range
+                 num_type     = float,
+                 round_coords = True):
+
+    if round_coords:
+        num = lambda: num_type(round(random() * (rng[1]-rng[0]) + rng[0]))
+    else:
+        num = lambda: num_type(random() * (rng[1]-rng[0]) + rng[0])
+    
+    return tuple( num() for axis in xrange(dim) )
+
+def random_list(length = SEQ_LENGTH, ring = False, **kwargs):
+    result  = [ random_coord(**kwargs) for index in xrange(length) ]
+    if ring:
+        result[-1] = result[0]
+
+    return result
+
+random_list.single = random_coord
+
+def random_coll(count = SEQ_LENGTH, **kwargs):
+    return [ tuple(random_list(**kwargs)) for i in xrange(count) ]
+
+random_coll.single = random_list
+
+class PyMutTestGeom:
+    "The Test Geometry class container."
+    def __init__(self, geom_type, coords_fcn=random_list, subtype=tuple, **kwargs):
+        self.geom_type  = geom_type
+        self.subtype    = subtype
+        self.coords_fcn = coords_fcn
+        self.fcn_args   = kwargs
+        self.coords = self.coords_fcn(**kwargs)
+        self.geom   = self.make_geom()
+
+    def newitem(self, **kwargs):
+        a = self.coords_fcn.single(**kwargs)
+        return self.subtype(a), tuple(a)
+
+    @property
+    def tuple_coords(self):
+        return tuple(self.coords)
+
+    def make_geom(self):
+        return self.geom_type(map(self.subtype,self.coords))
+
+
+def slice_geometries(ring=True): 
+    testgeoms = [
+        PyMutTestGeom(LineString),
+        PyMutTestGeom(MultiPoint, subtype=Point),
+        PyMutTestGeom(MultiLineString, coords_fcn=random_coll, subtype=LineString),
+        ]
+    if ring:
+        testgeoms.append(PyMutTestGeom(LinearRing, ring=True))
+
+    return testgeoms
+
+def getslice_functions(): 
+    def gs_01(x): x[0:4],   
+    def gs_02(x): x[5:-1],  
+    def gs_03(x): x[6:2:-1],
+    def gs_04(x): x[:],     
+    def gs_05(x): x[:3],    
+    def gs_06(x): x[::2],   
+    def gs_07(x): x[::-4],  
+    def gs_08(x): x[7:7],   
+    def gs_09(x): x[20:],   
+
+    # don't really care about ringy-ness here
+    return mark_ring(vars(), 'gs_')
+
+def delslice_functions():
+    def ds_01(x): del x[0:4]   
+    def ds_02(x): del x[5:-1]  
+    def ds_03(x): del x[6:2:-1]
+    def ds_04(x): del x[:]     # should this be allowed?
+    def ds_05(x): del x[:3]    
+    def ds_06(x): del x[1:9:2]   
+    def ds_07(x): del x[::-4]  
+    def ds_08(x): del x[7:7]   
+    def ds_09(x): del x[-7:-2]
+
+    return mark_ring(vars(), 'ds_')
+
+def setslice_extended_functions(g):
+    a = g.coords_fcn(3, rng=(100,150))
+    def maptype(x,a):
+        if isinstance(x, list): return a
+        else: return map(g.subtype, a)
+
+    def sse_00(x): x[:3:1]      = maptype(x, a)
+    def sse_01(x): x[0:3:1]     = maptype(x, a)
+    def sse_02(x): x[2:5:1]     = maptype(x, a)
+    def sse_03(x): x[-3::1]     = maptype(x, a)
+    def sse_04(x): x[-4:-1:1]   = maptype(x, a)
+    def sse_05(x): x[8:5:-1]    = maptype(x, a)
+    def sse_06(x): x[-6:-9:-1]  = maptype(x, a)
+    def sse_07(x): x[:8:3]      = maptype(x, a)
+    def sse_08(x): x[1::3]      = maptype(x, a)
+    def sse_09(x): x[-2::-3]    = maptype(x, a)
+    def sse_10(x): x[7:1:-2]    = maptype(x, a)
+    def sse_11(x): x[2:8:2]     = maptype(x, a)
+
+    return mark_ring(vars(), 'sse_')
+
+def setslice_simple_functions(g):
+    a = g.coords_fcn(3, rng=(100,150))
+    def maptype(x,a):
+        if isinstance(x, list): return a
+        else: return map(g.subtype, a)
+
+    def ss_00(x): x[:0]  = maptype(x, a)
+    def ss_01(x): x[:1]  = maptype(x, a)
+    def ss_02(x): x[:2]  = maptype(x, a)
+    def ss_03(x): x[:3]  = maptype(x, a)
+    def ss_04(x): x[-4:] = maptype(x, a)
+    def ss_05(x): x[-3:] = maptype(x, a)
+    def ss_06(x): x[-2:] = maptype(x, a)
+    def ss_07(x): x[-1:] = maptype(x, a)
+    def ss_08(x): x[5:]  = maptype(x, a)
+    def ss_09(x): x[:]   = maptype(x, a)
+    def ss_10(x): x[4:4] = maptype(x, a)
+    def ss_11(x): x[4:5] = maptype(x, a)
+    def ss_12(x): x[4:7] = maptype(x, a)
+    def ss_13(x): x[4:8] = maptype(x, a)
+    def ss_14(x): x[10:] = maptype(x, a)
+    def ss_15(x): x[20:30]  = maptype(x, a)
+    def ss_16(x): x[-13:-8] = maptype(x, a)
+    def ss_17(x): x[-13:-9] = maptype(x, a)
+    def ss_18(x): x[-13:-10] = maptype(x, a)
+    def ss_19(x): x[-13:-11] = maptype(x, a)
+
+    return mark_ring(vars(), 'ss_')
+
+def test_geos_functions():
+
+    return (
+        lambda x: x.num_coords,
+        lambda x: x.empty,
+        lambda x: x.valid,
+        lambda x: x.simple,
+        lambda x: x.ring,
+        lambda x: x.boundary,
+        lambda x: x.convex_hull,
+        lambda x: x.extend,
+        lambda x: x.area,
+        lambda x: x.length,
+            )
+
+def mark_ring(locals, name_pat, length=SEQ_LENGTH):
+    '''
+    Accepts an array of functions which perform slice modifications
+    and labels each function as to whether or not it preserves ring-ness
+    '''
+    func_array = [ val for name, val in locals.items()
+                    if hasattr(val, '__call__')
+                    and name.startswith(name_pat) ]
+
+    for i in xrange(len(func_array)):
+        a = range(length)
+        a[-1] = a[0]
+        func_array[i](a)
+        ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0])
+        func_array[i].ring = ring
+
+    return func_array
+
+def getcoords(o):
+    if hasattr(o, 'coords'):
+        return o.coords
+    else:
+        return o
+

Property changes on: tests/pymutable_geometries.py
___________________________________________________________________
Name: svn:executable
   + *

