/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/table.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/Model/table.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 839 - (show annotations)
Tue May 6 15:54:18 2003 UTC (21 years, 10 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/table.py
File MIME type: text/x-python
File size: 10970 byte(s)
Convert all table users to use the new table interface. This only
covers Thuban itself, not GREAT-ER or other applications built on
Thuban yet, so the compatibility interface stays in place for the
time being but it now issues DeprecationWarnings.

Finally, the new Table interface has a new method, HasColumn.

* Thuban/Model/table.py (OldTableInterfaceMixin): All methods
issue deprecation warnings when they're. The warnings refer to the
caller of the method.
(OldTableInterfaceMixin.__deprecation_warning): New. Helper method
for the deprecation warnings

* test/test_table.py: Ignore the deprecation warnings for the old
table in the tests in this module. The purpose of the tests is to
test the old interface, after all.

* test/test_transientdb.py
(TestTransientTable.run_iceland_political_tests): Use the
constants for the types. Add a test for HasColumn
(TestTransientTable.test_transient_joined_table): Adapt to new
table interface. Add a test for HasColumn
(TestTransientTable.test_transient_table_read_twice): Adapt to new
table interface

* Thuban/Model/transientdb.py (TransientTableBase.HasColumn)
(AutoTransientTable.HasColumn): Implement the new table interface
method
(AutoTransientTable.ReadRowAsDict, AutoTransientTable.ValueRange)
(AutoTransientTable.UniqueValues): Adapt to new table interface

* Thuban/Model/layer.py (Layer.SetShapeStore, Layer.GetFieldType):
Adapt to new table interface

* test/test_layer.py (TestLayer.open_shapefile): Helper method to
simplify opening shapefiles a bit easier.
(TestLayer.test_arc_layer, TestLayer.test_polygon_layer)
(TestLayer.test_point_layer): Use the new helper method
(TestLayer.test_get_field_type): New. Test for the GetFieldType
method

* test/test_dbf_table.py (TestDBFTable.test_has_column): Test for
the new table method

* test/test_memory_table.py (TestMemoryTable.test_has_column):
Test for the new table method HasColumn

1 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Jan-Oliver Wagner <[email protected]>
5 #
6 # This program is free software under the GPL (>=v2)
7 # Read the file COPYING coming with Thuban for details.
8
9 """
10 Classes for handling tables of data.
11 """
12
13 __version__ = "$Revision$"
14
15 import inspect
16 import warnings
17
18 import dbflib
19
20 # the field types supported by a Table instance.
21 FIELDTYPE_INT = "int"
22 FIELDTYPE_STRING = "string"
23 FIELDTYPE_DOUBLE = "double"
24
25
26 # map the dbflib constants for the field types to our constants
27 dbflib_fieldtypes = {dbflib.FTString: FIELDTYPE_STRING,
28 dbflib.FTInteger: FIELDTYPE_INT,
29 dbflib.FTDouble: FIELDTYPE_DOUBLE}
30
31
32 class OldTableInterfaceMixin:
33
34 """Mixin to implement the old table interface using the new one"""
35
36 def __deprecation_warning(self):
37 """Issue a DeprecationWarning for code hat uses the old interface"""
38 callername = inspect.currentframe().f_back.f_code.co_name
39 warnings.warn("The %s method of the old table interface"
40 " is deprecated" % callername,
41 DeprecationWarning, stacklevel = 3)
42
43 def record_count(self):
44 self.__deprecation_warning()
45 return self.NumRows()
46
47 def field_count(self):
48 self.__deprecation_warning()
49 return self.NumColumns()
50
51 def field_info(self, field):
52 """Return a tuple (type, name, width, prec) for the field no. field
53
54 type is the data type of the field, name the name, width the
55 field width in characters and prec the decimal precision. width
56 and prec will be zero if the information returned by the Column
57 method doesn't provide values for them.
58 """
59 self.__deprecation_warning()
60 col = self.Column(field)
61 return (col.type, col.name,
62 getattr(col, "width", 0), getattr(col, "prec", 0))
63
64 def field_info_by_name(self, col):
65 self.__deprecation_warning()
66 try:
67 return self.field_info(col)
68 except KeyError:
69 # FIXME: It may be that field_info raises other exceptions
70 # when the name is not a valid column name.
71 return None
72
73 def field_range(self, fieldName):
74 self.__deprecation_warning()
75 min, max = self.ValueRange(fieldName)
76 return ((min, None), (max, None))
77
78 def GetUniqueValues(self, field):
79 self.__deprecation_warning()
80 return self.UniqueValues(field)
81
82 def read_record(self, r):
83 self.__deprecation_warning()
84 return self.ReadRowAsDict(r)
85
86
87
88 class DBFColumn:
89
90 """Description of a column in a DBFTable
91
92 Instances have the following public attributes:
93
94 name -- Name of the column
95 type -- Type of the column (one of FIELDTYPE_STRING, FIELDTYPE_INT or\
96 FIELDTYPE_DOUBLE)
97 index -- The index of the column
98 width -- the width of the data in the column
99 prec -- The precision of the data (only valid for type == FIELDTYPE_DOUBLE)
100 """
101
102 def __init__(self, name, type, width, prec, index):
103 self.name = name
104 self.type = type
105 self.width = width
106 self.prec = prec
107 self.index = index
108
109
110 class DBFTable(OldTableInterfaceMixin):
111
112 """
113 Table interface for the data in a DBF file
114 """
115
116 # Implementation strategy regarding writing to a DBF file:
117 #
118 # Most of the time Thuban only needs to read from a table and it is
119 # important that Thuban can work with read-only files. Therefore the
120 # DBF file is opened only for reading initially. Only when
121 # write_record is called we try to open the DBF file for writing as
122 # well. If that succeeds the read/write DBF file will be used for
123 # all IO afterwards.
124 #
125 # It's important to use the same DBF file object for both reading
126 # and writing to make sure that reading a records after writing
127 # returns the new values. With two separate objects this wouldn't
128 # work because a DBF file object buffers some data
129
130 def __init__(self, filename):
131 self.filename = filename
132 self.dbf = dbflib.DBFFile(filename)
133
134 # If true, self.dbf is open for writing.
135 self._writable = 0
136
137 # Create the column information objects
138 self.columns = []
139 self.column_map = {}
140 for i in range(self.NumColumns()):
141 ftype, name, width, prec = self.dbf.field_info(i)
142 ftype = dbflib_fieldtypes[ftype]
143 index = len(self.columns)
144 col = DBFColumn(name, ftype, width, prec, index)
145 self.columns.append(col)
146 self.column_map[name] = col
147 self.column_map[index] = col
148
149 def NumRows(self):
150 """Return the number of rows in the table"""
151 return self.dbf.record_count()
152
153 def NumColumns(self):
154 """Return the number of columns in the table"""
155 return self.dbf.field_count()
156
157 def Columns(self):
158 """Return the table's colum definitions
159
160 The return value is a sequence of DBFColumn instances, one for
161 each column.
162 """
163 return self.columns
164
165 def Column(self, col):
166 """Return information about the column given by its name or index
167
168 The returned object is an instance of DBFColumn
169 """
170 return self.column_map[col]
171
172 def HasColumn(self, col):
173 """Return whether the table has a column with the given name or index
174 """
175 return self.column_map.has_key(col)
176
177 def ReadRowAsDict(self, row):
178 """Return the entire row as a dictionary with column names as keys"""
179 return self.dbf.read_record(row)
180
181 def ReadValue(self, row, col):
182 """Return the value of the specified row and column
183
184 The col parameter may be the index of the column or its name.
185 """
186 return self.dbf.read_record(row)[self.column_map[col].name]
187
188 def ValueRange(self, col):
189 """Return the minimum and maximum values of the values in the column
190
191 The return value is a tuple (min, max) unless the table is empty
192 in which case the return value is None.
193 """
194 count = self.NumRows()
195
196 if count == 0:
197 return None
198
199 min = max = self.ReadValue(0, col)
200 for i in range(1, count):
201 value = self.ReadValue(i, col)
202 if value < min:
203 min = value
204 elif value > max:
205 max = value
206
207 return (min, max)
208
209 def UniqueValues(self, col):
210 """Return a sorted list of all unique values in the column col"""
211 dict = {}
212
213 for i in range(self.NumRows()):
214 value = self.ReadValue(i, col)
215 dict[value] = 0
216
217 values = dict.keys()
218 values.sort()
219 return values
220
221
222 # DBF specific interface parts.
223
224 def Destroy(self):
225 self.dbf.close()
226 self.dbf = None
227
228 def write_record(self, record, values):
229 """Write the values into the record
230
231 The values parameter may either be a dictionary or a sequence.
232
233 If it's a dictionary the keys must be the names of the fields
234 and their value must have a suitable type. Only the fields
235 actually contained in the dictionary are written. Fields for
236 which there's no item in the dict are not modified.
237
238 If it's a sequence, all fields must be present in the right
239 order.
240 """
241 if not self._writable:
242 new_dbf = dbflib.DBFFile(self.filename, "r+b")
243 self.dbf.close()
244 self.dbf = new_dbf
245 self._writable = 1
246 self.dbf.write_record(record, values)
247 self.dbf.commit()
248
249
250
251 # Temporary backwards compatibility
252 Table = DBFTable
253
254
255
256 class MemoryColumn:
257
258 def __init__(self, name, type, index):
259 self.name = name
260 self.type = type
261 self.index = index
262
263 class MemoryTable(OldTableInterfaceMixin):
264
265 """Very simple table implementation that operates on a list of tuples"""
266
267 def __init__(self, fields, data):
268 """Initialize the MemoryTable
269
270 Parameters:
271 fields -- List of (name, field_type) pairs
272 data -- List of tuples, one for each row of data
273 """
274 self.data = data
275
276 # Create the column information objects
277 self.columns = []
278 self.column_map = {}
279 for name, ftype in fields:
280 index = len(self.columns)
281 col = MemoryColumn(name, ftype, index)
282 self.columns.append(col)
283 self.column_map[name] = col
284 self.column_map[index] = col
285
286 def NumColumns(self):
287 """Return the number of columns in the table"""
288 return len(self.columns)
289
290 def Column(self, col):
291 """Return information about the column given by its name or index
292
293 The returned object is an instance of MemoryColumn.
294 """
295 return self.column_map[col]
296
297 def Columns(self):
298 """Return the table's colum definitions
299
300 The return value is a sequence of MemoryColumn instances, one
301 for each column.
302 """
303 return self.columns
304
305 def HasColumn(self, col):
306 """Return whether the table has a column with the given name or index
307 """
308 return self.column_map.has_key(col)
309
310 def NumRows(self):
311 """Return the number of rows in the table"""
312 return len(self.data)
313
314 def ReadValue(self, row, col):
315 """Return the value of the specified row and column
316
317 The col parameter may be the index of the column or its name.
318 """
319 return self.data[row][self.column_map[col].index]
320
321 def ReadRowAsDict(self, index):
322 """Return the entire row as a dictionary with column names as keys"""
323 return dict([(col.name, self.data[index][col.index])
324 for col in self.columns])
325
326 def ValueRange(self, col):
327 """Return the minimum and maximum values of the values in the column
328
329 The return value is a tuple (min, max) unless the table is empty
330 in which case the return value is None.
331 """
332
333 index = self.column_map[col].index
334 values = [row[index] for row in self.data]
335 if not values:
336 return None
337
338 return min(values), max(values)
339
340 def UniqueValues(self, col):
341 """Return a sorted list of all unique values in the column col"""
342 dict = {}
343
344 for i in range(self.NumRows()):
345 value = self.ReadValue(i, col)
346 dict[value] = 0
347
348 values = dict.keys()
349 values.sort()
350 return values
351
352
353 def write_record(self, record, values):
354 # TODO: Check for correct lenght and perhaps also
355 # for correct types in case values is a tuple. How to report problems?
356 # TODO: Allow values to be a dictionary and write the single
357 # fields that are specified.
358 self.data[record] = values

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26