Ticket #9877: pythonic_mutation_patch_part_I

File pythonic_mutation_patch_part_I, 17.8 KB (added by Aryeh Leib Taurog <vim@…>, 16 years ago)

This is a patch to the django 1.0.2 release

Line 
1Index: geos/geometries.py
2===================================================================
3--- geos/geometries.py (revision 2)
4+++ geos/geometries.py (revision 7)
5@@ -156,10 +156,12 @@
6
7 # Getting the correct initialization function
8 if kwargs.get('ring', False):
9- func = create_linearring
10+ self._init_func = create_linearring
11 else:
12- func = create_linestring
13+ self._init_func = create_linestring
14
15+ func = self._init_func
16+
17 # If SRID was passed in with the keyword arguments
18 srid = kwargs.get('srid', None)
19
20@@ -168,13 +170,152 @@
21 super(LineString, self).__init__(func(cs.ptr), srid=srid)
22
23 def __getitem__(self, index):
24- "Gets the point at the specified index."
25- return self._cs[index]
26+ "Gets the coordinates of the point(s) at the specified index/slice."
27+ if isinstance(index, slice):
28+ return [self._cs[i] for i in xrange(*index.indices(len(self._cs)))]
29+ else:
30+ if index < 0:
31+ index += len(self._cs)
32+ return self._cs[index]
33
34+ def __delitem__(self, index):
35+ "Delete the point(s) at the specified index/slice."
36+ if not isinstance(index, (int, long, slice)):
37+ raise TypeError("%s is not a legal index" % index)
38+
39+ # calculate new length and dimensions
40+ currLen = len(self._cs)
41+ if isinstance(index, (int, long)):
42+ if index < 0: index += currLen
43+ if index < 0 or currLen <= index:
44+ raise GEOSIndexError('invalid GEOS Geometry index: %d' % index)
45+ indexRange = [index]
46+ else:
47+ indexRange = range(*index.indices(currLen))
48+
49+ newLen = currLen - len(indexRange)
50+ ndim = self._cs.dims
51+ hasz = self._cs.hasz # I don't understand why these are different
52+
53+ # create a new coordinate sequence and populate accordingly
54+ cs = GEOSCoordSeq(create_cs(newLen, ndim), z=hasz)
55+ new_i = 0
56+ for old_i in xrange(currLen):
57+ if old_i in indexRange: continue
58+ cs[new_i] = self._cs[old_i]
59+ new_i += 1
60+
61+ ptr = self._init_func(cs.ptr)
62+ if ptr:
63+ destroy_geom(self.ptr)
64+ self._ptr = ptr
65+ self._post_init(self.srid)
66+ else:
67+ # can this happen?
68+ raise GEOSException('Geometry resulting from slice deletion was invalid.')
69+
70 def __setitem__(self, index, value):
71- "Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
72- self._cs[index] = value
73+ """Sets the point(s) at the specified index/slice,
74+ e.g., line_str[0] = (1, 2)."""
75+ if isinstance(index, slice):
76+ try:
77+ valueIter = iter(value)
78+ except TypeError:
79+ raise TypeError('can only assign an iterable')
80
81+ # calculate length and dimensions
82+ currLen = len(self._cs)
83+ valueList = list(value)
84+ start, stop, step = index.indices(currLen)
85+ stop = max(0, stop) # stop will be -1 if out-of-bounds
86+ # negative index is given
87+
88+ # CAREFUL: index.step and step are not the same!
89+ # step will never be None
90+ #
91+ if index.step is None:
92+ # this is a simple slice, can assign slice of any length
93+ # calculate new length
94+ newLen = currLen - stop + start + len(valueList)
95+ ndim = self._cs.dims
96+ hasz = self._cs.hasz # not sure why these are different
97+
98+ # create a new coordinate sequence and populate accordingly
99+ cs = GEOSCoordSeq(create_cs(newLen, ndim), z=hasz)
100+ new_i = 0
101+ for old_i in xrange(currLen + 1):
102+ if old_i == start:
103+ for val in valueList:
104+ cs[new_i] = val
105+ new_i += 1
106+
107+ if old_i < currLen:
108+ if old_i < start or old_i >= stop:
109+ cs[new_i] = self._cs[old_i]
110+ new_i += 1
111+
112+ ptr = self._init_func(cs.ptr)
113+ # Polygon.__setitem__ doesn't check this, but it seems
114+ # that it can't hurt.
115+ if ptr:
116+ destroy_geom(self.ptr)
117+ self._ptr = ptr
118+ self._post_init(self.srid)
119+ else:
120+ raise GEOSException('Geometry resulting from slice deletion was invalid.')
121+
122+ else:
123+ indexList = range(start, stop, step)
124+ # extended slice, only allow assigning slice of same size
125+ if len(valueList) != len(indexList):
126+ raise ValueError('attempt to assign sequence of size %d '
127+ 'to extended slice of size %d'
128+ % (len(valueList), len(indexList)))
129+
130+ # we're not changing the length of the sequence
131+ # we can just iterate the indices and value and set them
132+ for i, val in zip(indexList , valueList):
133+ self._cs[i] = val
134+
135+ else:
136+ length = len(self._cs)
137+ if index < 0: index += length
138+ self._cs[index] = value
139+
140+ def append(self, val):
141+ "Standard list append method"
142+ self[len(self):] = [val]
143+
144+ def extend(self, vals):
145+ "Standard list extend method"
146+ self[len(self):] = vals
147+
148+ def insert(self, index, val):
149+ "Standard list insert method"
150+ if not isinstance(index, (int, long)):
151+ raise TypeError("%s is not a legal index" % index)
152+ self[index:index] = [val]
153+
154+ def pop(self, index=-1):
155+ "Standard list pop method"
156+ result = self[index]
157+ del self[index]
158+ return result
159+
160+ def index(self, val):
161+ "Standard list index method"
162+ for i in xrange(0, len(self)):
163+ if self[i] == val: return i
164+ raise ValueError('%s not in geometry' % str(val))
165+
166+ def remove(self, val):
167+ "Standard list remove method"
168+ del self[self.index(val)]
169+
170+ def count(self):
171+ "Standard list count method"
172+ return len(self)
173+
174 def __iter__(self):
175 "Allows iteration over this LineString."
176 for i in xrange(len(self)):
177Index: tests/__init__.py
178===================================================================
179--- tests/__init__.py (revision 2)
180+++ tests/__init__.py (revision 7)
181@@ -18,6 +18,7 @@
182 # Tests that do not require setting up and tearing down a spatial database.
183 test_suite_names = [
184 'test_geos',
185+ 'test_geos_pymutable',
186 'test_measure',
187 ]
188 if HAS_GDAL:
189Index: tests/test_geos_pymutable.py
190===================================================================
191--- tests/test_geos_pymutable.py (revision 0)
192+++ tests/test_geos_pymutable.py (revision 7)
193@@ -0,0 +1,174 @@
194+import unittest
195+from pymutable_geometries import *
196+from django.contrib.gis.geos.error import GEOSIndexError
197+
198+class GEOSPyMutableTest(unittest.TestCase):
199+ '''
200+ Tests Pythonic Mutability of Python GEOS geometry wrappers
201+ get/set/delitem on a slice, normal list methods
202+ '''
203+
204+ def test01_getslice(self):
205+ 'Test getting a slice from a geometry'
206+ for f in getslice_functions():
207+ for g in slice_geometries():
208+ self.assertEqual(f(g.coords), f(g.geom), f.__name__)
209+
210+ def test02_getitem(self):
211+ 'Test getting a single item from a geometry'
212+ for g in slice_geometries():
213+ for i in seqrange():
214+ self.assertEqual(g.coords[i], g.geom[i])
215+
216+ def test03_getitem_indexException(self):
217+ 'Test get single item with out-of-bounds index'
218+ for g in slice_geometries():
219+ for i in SEQ_OUT_OF_BOUNDS:
220+ self.assertRaises(GEOSIndexError, lambda: g.geom[i])
221+
222+ def test04_delitem_single(self):
223+ 'Test delete single item from a geometry'
224+ for i in seqrange():
225+ for g in slice_geometries():
226+ if g.geom.ring and i in SEQ_BOUNDS: continue
227+ del g.coords[i]
228+ del g.geom[i]
229+ self.assertEqual(g.coords , g.geom[:])
230+
231+ def test05_delitem_slice(self):
232+ 'Test delete slice from a geometry'
233+ for f in delslice_functions():
234+ for g in slice_geometries():
235+ if g.geom.ring and not f.ring: continue
236+ f(g.coords)
237+ f(g.geom)
238+ self.assertEqual(g.coords , g.geom[:], f.__name__)
239+
240+ def test06_delitem_single_indexException(self):
241+ 'Test delete single item with out-of-bounds index'
242+ def func(x, i): del x[i]
243+ for g in slice_geometries():
244+ for i in SEQ_OUT_OF_BOUNDS:
245+ self.assertRaises(GEOSIndexError, func, g.geom, i)
246+
247+ def test07_setitem_single(self):
248+ "Test set single item (make sure we didn't break this)"
249+ for i in seqrange():
250+ for g in slice_geometries():
251+ if g.geom.ring and i in SEQ_BOUNDS: continue
252+ g.coords[i] = (3.14159, 3.14159)
253+ g.geom[i] = (3.14159, 3.14159)
254+ self.assertEqual(g.coords , g.geom[:])
255+
256+ def test08_setslice_simple(self):
257+ 'Test setting a simple slice of a geometry'
258+ for f in setslice_simple_functions():
259+ for g in slice_geometries():
260+ if g.geom.ring and not f.ring: continue
261+ f(g.coords)
262+ f(g.geom)
263+ self.assertEqual(g.coords , g.geom[:], f.__name__)
264+
265+ def test09_setslice_extended(self):
266+ 'Test setting an extended slice of a geometry'
267+ for f in setslice_extended_functions():
268+ for g in slice_geometries():
269+ if g.geom.ring and not f.ring: continue
270+ f(g.coords)
271+ f(g.geom)
272+ self.assertEqual(g.coords , g.geom[:], f.__name__)
273+
274+ def test10_setslice_extended_mismatched(self):
275+ 'Test setting extended slice with array of mismatched length'
276+ def func(x): x[2:8:2] = [(3.1415, 3.1415)]
277+ for g in slice_geometries():
278+ self.assertRaises(ValueError, func, g.geom)
279+
280+ def test11_setitem_single_indexException(self):
281+ 'Test set single item with out-of-bounds index'
282+ def func(x, i): x[i] = (3.1415, 3.1415)
283+ for g in slice_geometries():
284+ for i in SEQ_OUT_OF_BOUNDS:
285+ self.assertRaises(GEOSIndexError, func, g.geom, i)
286+
287+ def test12_append(self):
288+ 'Test list method append'
289+ for g in slice_geometries():
290+ if g.geom.ring: continue
291+ g.geom.append((200.0,200.0))
292+ g.coords.append((200.0,200.0))
293+ self.assertEqual(g.coords , g.geom[:])
294+
295+ def test13_extend(self):
296+ 'Test list method extend'
297+ for g in slice_geometries():
298+ points = random_coords(5)
299+ if g.geom.ring: points[-1] = g.coords[0]
300+ g.geom.extend(points)
301+ g.coords.extend(points)
302+ self.assertEqual(g.coords , g.geom[:])
303+
304+ def test14_insert(self):
305+ 'Test list method insert'
306+ for i in xrange(*SEQ_OUT_OF_BOUNDS):
307+ for g in slice_geometries():
308+ if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
309+ continue
310+ g.geom.insert(i, (3141.5, 3141.5))
311+ g.coords.insert(i, (3141.5, 3141.5))
312+ self.assertEqual(g.coords , g.geom[:])
313+
314+ def test15_insert_typeError(self):
315+ 'Test list method insert raises error on invalid index'
316+ for g in slice_geometries():
317+ self.assertRaises(TypeError, g.geom.insert,
318+ 'hi', (3141.5, 3141.5))
319+
320+ def test16_pop(self):
321+ 'Test list method pop'
322+ for i in seqrange():
323+ for g in slice_geometries():
324+ if g.geom.ring and i in SEQ_BOUNDS + SEQ_OUT_OF_BOUNDS:
325+ continue
326+ self.assertEqual(g.coords.pop(i), g.geom.pop(i))
327+
328+ def test16_index(self):
329+ 'Test list method index'
330+ for i in xrange(0, SEQ_LENGTH):
331+ for g in slice_geometries():
332+ if g.geom.ring and i in SEQ_BOUNDS: continue
333+ p = (200.0, 200.0)
334+ g.geom[i] = p
335+ self.assertEqual(i, g.geom.index(p))
336+
337+ def test17_index_ValueError(self):
338+ 'Test list method raises ValueError if value not found'
339+ for g in slice_geometries():
340+ self.assertRaises(ValueError, g.geom.index, (200.0,200.0))
341+
342+ def test18_remove(self):
343+ 'Test list method remove'
344+ for i in xrange(0, SEQ_LENGTH):
345+ for g in slice_geometries():
346+ if g.geom.ring and i in SEQ_BOUNDS: continue
347+ p = (200.0, 200.0)
348+ g.geom[i] = p
349+ g.coords[i] = p
350+ g.geom.remove(p)
351+ g.coords.remove(p)
352+ self.assertEqual(g.coords, g.geom[:])
353+
354+ def test19_count(self):
355+ 'Test list method count'
356+ for g in slice_geometries():
357+ self.assertEqual(SEQ_LENGTH, g.geom.count())
358+def suite():
359+ s = unittest.TestSuite()
360+ s.addTest(unittest.makeSuite(GEOSPyMutableTest))
361+ return s
362+
363+def run(verbosity=2):
364+ unittest.TextTestRunner(verbosity=verbosity).run(suite())
365+
366+if __name__ == '__main__':
367+ run()
368
369Property changes on: tests/test_geos_pymutable.py
370___________________________________________________________________
371Name: svn:executable
372 + *
373
374Index: tests/pymutable_geometries.py
375===================================================================
376--- tests/pymutable_geometries.py (revision 0)
377+++ tests/pymutable_geometries.py (revision 7)
378@@ -0,0 +1,125 @@
379+from django.contrib.gis.geos import *
380+from random import random
381+
382+SEQ_LENGTH = 10
383+SEQ_RANGE = (-1 * SEQ_LENGTH, SEQ_LENGTH)
384+SEQ_BOUNDS = (-1 * SEQ_LENGTH, -1, 0, SEQ_LENGTH - 1)
385+SEQ_OUT_OF_BOUNDS = (-1 * SEQ_LENGTH -1 , SEQ_LENGTH)
386+
387+def seqrange(): return xrange(*SEQ_RANGE)
388+
389+class PyMutTestGeom:
390+ "The Test Geometry class container."
391+ def __init__(self, geom_type, coords, **kwargs):
392+ self.coords = coords
393+ self.geom = geom_type(coords)
394+
395+ for key, value in kwargs.items():
396+ setattr(self, key, value)
397+
398+def random_coords(length=SEQ_LENGTH, dim=2, rng=(-50,50), type=float,
399+ ring=False, round_coords=True):
400+ if round_coords:
401+ num = lambda: type(round(random() * (rng[1]-rng[0]) + rng[0]))
402+ else:
403+ num = lambda: type(random() * (rng[1]-rng[0]) + rng[0])
404+
405+ result = [ tuple( num() for axis in xrange(dim) )
406+ for index in xrange(length) ]
407+ if ring:
408+ result[-1] = result[0]
409+
410+ return result
411+
412+
413+def slice_geometries():
414+ return (
415+ PyMutTestGeom(LineString, random_coords()),
416+ PyMutTestGeom(LinearRing, random_coords(ring=True)),
417+ )
418+
419+def getslice_functions():
420+ return (
421+ lambda x: x[0:4],
422+ lambda x: x[5:-1],
423+ lambda x: x[6:2:-1],
424+ lambda x: x[:],
425+ lambda x: x[:3],
426+ lambda x: x[::2],
427+ lambda x: x[::-4],
428+ lambda x: x[7:7],
429+ lambda x: x[20:],
430+ )
431+
432+def delslice_functions():
433+ def ds_01(x): del x[0:4]
434+ def ds_02(x): del x[5:-1]
435+ def ds_03(x): del x[6:2:-1]
436+ def ds_04(x): del x[:] # should this be allowed?
437+ def ds_05(x): del x[:3]
438+ def ds_06(x): del x[1:9:2]
439+ def ds_07(x): del x[::-4]
440+ def ds_08(x): del x[7:7]
441+ def ds_09(x): del x[-7:-2]
442+
443+ return mark_ring(vars(), 'ds_')
444+
445+def setslice_extended_functions():
446+ a = [(1.0,2.0),(1.0,2.0),(1.0,2.0)]
447+ def sse_00(x): x[:3:1] = a
448+ def sse_01(x): x[0:3:1] = a
449+ def sse_02(x): x[2:5:1] = a
450+ def sse_03(x): x[-3::1] = a
451+ def sse_04(x): x[-4:-1:1] = a
452+ def sse_05(x): x[8:5:-1] = a
453+ def sse_06(x): x[-6:-9:-1] = a
454+ def sse_07(x): x[:8:3] = a
455+ def sse_08(x): x[1::3] = a
456+ def sse_09(x): x[-2::-3] = a
457+ def sse_10(x): x[7:1:-2] = a
458+ def sse_11(x): x[2:8:2] = a
459+
460+ return mark_ring(vars(), 'sse_')
461+
462+def setslice_simple_functions():
463+ a = [(1.0,2.0),(2.0,1.0),(2.0,2.0)]
464+ def ss_00(x): x[:0] = a
465+ def ss_01(x): x[:1] = a
466+ def ss_02(x): x[:2] = a
467+ def ss_03(x): x[:3] = a
468+ def ss_04(x): x[-4:] = a
469+ def ss_05(x): x[-3:] = a
470+ def ss_06(x): x[-2:] = a
471+ def ss_07(x): x[-1:] = a
472+ def ss_08(x): x[5:] = a
473+ def ss_09(x): x[:] = a
474+ def ss_10(x): x[4:4] = a
475+ def ss_11(x): x[4:5] = a
476+ def ss_12(x): x[4:7] = a
477+ def ss_13(x): x[4:8] = a
478+ def ss_14(x): x[20:30] = a
479+ def ss_15(x): x[-13:-8] = a
480+ def ss_16(x): x[-13:-9] = a
481+ def ss_17(x): x[-13:-10] = a
482+ def ss_18(x): x[-13:-11] = a
483+ def ss_19(x): x[10:] = a
484+
485+ return mark_ring(vars(), 'ss_')
486+
487+def mark_ring(locals, name_pat, length=SEQ_LENGTH):
488+ '''
489+ Accepts an array of functions which perform slice modifications
490+ and labels each function as to whether or not it preserves ring-ness
491+ '''
492+ func_array = [ val for name, val in locals.items()
493+ if hasattr(val, '__call__')
494+ and name.startswith(name_pat) ]
495+
496+ for i in xrange(len(func_array)):
497+ a = range(length)
498+ a[-1] = a[0]
499+ func_array[i](a)
500+ ring = len(a) == 0 or (len(a) > 3 and a[-1] == a[0])
501+ func_array[i].ring = ring
502+
503+ return func_array
504
505Property changes on: tests/pymutable_geometries.py
506___________________________________________________________________
507Name: svn:executable
508 + *
509
Back to Top