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

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/load.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2004 - (hide annotations)
Tue Dec 2 13:25:55 2003 UTC (21 years, 3 months ago) by bh
Original Path: trunk/thuban/Thuban/Model/load.py
File MIME type: text/x-python
File size: 23317 byte(s)
* Thuban/Model/save.py (SessionSaver.write_session): Save files
with the thuban-1.0rc1

* Thuban/Model/load.py (SessionLoader.__init__): Recognize the
thuban-1.0rc1 namespace too

* test/test_save.py (SaveSessionTest.dtd)
(SaveSessionTest.testEmptySession)
(SaveSessionTest.testSingleLayer)
(SaveSessionTest.testLayerProjection)
(SaveSessionTest.testRasterLayer)
(SaveSessionTest.testClassifiedLayer)
(SaveSessionTest.test_dbf_table)
(SaveSessionTest.test_joined_table)
(SaveSessionTest.test_save_postgis): Update to thuban-1.0rc1
namespace

* test/test_load.py (LoadSessionTest.dtd): Update to thuban-1.0rc1
namespace
(TestSingleLayer.file_contents)
(TestNonAsciiColumnName.file_contents)
(TestLayerVisibility.file_contents)
(TestClassification.file_contents, TestLabels.file_contents)
(TestLayerProjection.file_contents)
(TestRasterLayer.file_contents, TestJoinedTable.file_contents)
(TestPostGISLayer.file_contents)
(TestPostGISLayerPassword.file_contents)
(TestLoadError.file_contents, TestLoadError.test): Update to
thuban-1.0rc1 namespace

1 bh 723 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4 bh 267 # Bernhard Herzog <[email protected]>
5 jonathan 413 # Jonathan Coles <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with GRASS for details.
9    
10     """
11     Parser for thuban session files.
12     """
13    
14     __version__ = "$Revision$"
15    
16 bh 723 import string, os
17 bh 267
18     import xml.sax
19     import xml.sax.handler
20 jonathan 526 from xml.sax import make_parser, ErrorHandler, SAXNotRecognizedException
21 bh 267
22 jan 374 from Thuban import _
23 jonathan 413
24 jonathan 473 from Thuban.Model.table import FIELDTYPE_INT, FIELDTYPE_DOUBLE, \
25     FIELDTYPE_STRING
26    
27 jonathan 1339 from Thuban.Model.color import Color, Transparent
28    
29 bh 6 from Thuban.Model.session import Session
30     from Thuban.Model.map import Map
31 jonathan 930 from Thuban.Model.layer import Layer, RasterLayer
32 bh 6 from Thuban.Model.proj import Projection
33 jonathan 874 from Thuban.Model.range import Range
34 jonathan 413 from Thuban.Model.classification import Classification, \
35 jonathan 439 ClassGroupDefault, ClassGroupSingleton, ClassGroupRange, ClassGroupMap, \
36     ClassGroupProperties
37 bh 1268 from Thuban.Model.data import DerivedShapeStore, ShapefileStore
38     from Thuban.Model.table import DBFTable
39     from Thuban.Model.transientdb import TransientJoinedTable
40 bh 6
41 bh 1646 from Thuban.Model.xmlreader import XMLReader
42     import resource
43    
44     import postgisdb
45    
46 bh 1268 class LoadError(Exception):
47    
48 bh 1646 """Exception raised when the thuban file is corrupted
49 bh 6
50 bh 1646 Not all cases of corrupted thuban files will lead to this exception
51     but those that are found by checks in the loading code itself are.
52     """
53    
54    
55     class LoadCancelled(Exception):
56    
57     """Exception raised to indicate that loading was interrupted by the user"""
58    
59    
60 bh 267 def parse_color(color):
61     """Return the color object for the string color.
62 bh 6
63 bh 267 Color may be either 'None' or of the form '#RRGGBB' in the usual
64     HTML color notation
65 bh 6 """
66     color = string.strip(color)
67     if color == "None":
68 jonathan 1339 result = Transparent
69 bh 6 elif color[0] == '#':
70     if len(color) == 7:
71     r = string.atoi(color[1:3], 16) / 255.0
72     g = string.atoi(color[3:5], 16) / 255.0
73     b = string.atoi(color[5:7], 16) / 255.0
74     result = Color(r, g, b)
75     else:
76 jan 374 raise ValueError(_("Invalid hexadecimal color specification %s")
77 bh 6 % color)
78     else:
79 jan 374 raise ValueError(_("Invalid color specification %s") % color)
80 bh 6 return result
81    
82 bh 1268 class AttrDesc:
83    
84     def __init__(self, name, required = False, default = "",
85     conversion = None):
86     if not isinstance(name, tuple):
87     fullname = (None, name)
88     else:
89     fullname = name
90     name = name[1]
91     self.name = name
92     self.fullname = fullname
93     self.required = required
94     self.default = default
95     self.conversion = conversion
96    
97     # set by the SessionLoader's check_attrs method
98     self.value = None
99    
100    
101 jonathan 706 class SessionLoader(XMLReader):
102 jonathan 694
103 bh 1646 def __init__(self, db_connection_callback = None):
104 jonathan 694 """Inititialize the Sax handler."""
105 jonathan 706 XMLReader.__init__(self)
106 jonathan 694
107 bh 1646 self.db_connection_callback = db_connection_callback
108    
109 jonathan 694 self.theSession = None
110     self.aMap = None
111     self.aLayer = None
112    
113 bh 1268 # Map ids used in the thuban file to the corresponding objects
114     # in the session
115     self.idmap = {}
116 jonathan 706
117 bh 1268 dispatchers = {
118     'session' : ("start_session", "end_session"),
119 bh 1646
120     'dbconnection': ("start_dbconnection", None),
121    
122     'dbshapesource': ("start_dbshapesource", None),
123 bh 1268 'fileshapesource': ("start_fileshapesource", None),
124     'derivedshapesource': ("start_derivedshapesource", None),
125     'filetable': ("start_filetable", None),
126     'jointable': ("start_jointable", None),
127    
128     'map' : ("start_map", "end_map"),
129     'projection' : ("start_projection", "end_projection"),
130     'parameter' : ("start_parameter", None),
131     'layer' : ("start_layer", "end_layer"),
132     'rasterlayer' : ("start_rasterlayer", "end_rasterlayer"),
133     'classification': ("start_classification", "end_classification"),
134     'clnull' : ("start_clnull", "end_clnull"),
135     'clpoint' : ("start_clpoint", "end_clpoint"),
136     'clrange' : ("start_clrange", "end_clrange"),
137     'cldata' : ("start_cldata", "end_cldata"),
138     'table' : ("start_table", "end_table"),
139     'labellayer' : ("start_labellayer", None),
140     'label' : ("start_label", None)}
141    
142 bh 1375 # all dispatchers should be used for the 0.8 and 0.9 namespaces too
143     for xmlns in ("http://thuban.intevation.org/dtds/thuban-0.8.dtd",
144 bh 1664 "http://thuban.intevation.org/dtds/thuban-0.9-dev.dtd",
145 bh 1844 "http://thuban.intevation.org/dtds/thuban-0.9.dtd",
146 bh 2004 "http://thuban.intevation.org/dtds/thuban-1.0-dev.dtd",
147     "http://thuban.intevation.org/dtds/thuban-1.0rc1.dtd"):
148 bh 1375 for key, value in dispatchers.items():
149     dispatchers[(xmlns, key)] = value
150 bh 1268
151     XMLReader.AddDispatchers(self, dispatchers)
152    
153 bh 1930 def Destroy(self):
154     """Clear all instance variables to cut cyclic references.
155    
156     The GC would have collected the loader eventually but it can
157     happen that it doesn't run at all until Thuban is closed (2.3
158     but not 2.2 tries a bit harder and forces a collection when the
159     interpreter terminates)
160     """
161     self.__dict__.clear()
162    
163 bh 267 def start_session(self, name, qname, attrs):
164 bh 1268 self.theSession = Session(self.encode(attrs.get((None, 'title'),
165     None)))
166 bh 6
167 bh 267 def end_session(self, name, qname):
168     pass
169 bh 6
170 bh 1268 def check_attrs(self, element, attrs, descr):
171     """Check and convert some of the attributes of an element
172    
173     Parameters:
174     element -- The element name
175     attrs -- The attrs mapping as passed to the start_* methods
176     descr -- Sequence of attribute descriptions (AttrDesc instances)
177    
178     Return a dictionary containig normalized versions of the
179     attributes described in descr. The keys of that dictionary are
180     the name attributes of the attribute descriptions. The attrs
181     dictionary will not be modified.
182    
183     If the attribute is required, i.e. the 'required' attribute of
184     the descrtiption is true, but it is not in attrs, raise a
185     LoadError.
186    
187     If the attribute has a default value and it is not present in
188     attrs, use that default value as the value in the returned dict.
189    
190 bh 1970 The value is converted before putting it into the returned dict.
191     The following conversions are available:
192 bh 1268
193     'filename' -- The attribute is a filename.
194    
195     If the filename is a relative name, interpret
196     it relative to the directory containing the
197     .thuban file and make it an absolute name
198    
199     'shapestore' -- The attribute is the ID of a shapestore
200     defined earlier in the .thuban file. Look it
201     up self.idmap
202    
203     'table' -- The attribute is the ID of a table or shapestore
204     defined earlier in the .thuban file. Look it up
205     self.idmap. If it's the ID of a shapestore the
206     value will be the table of the shapestore.
207 bh 1646
208     'idref' -- The attribute is the id of an object defined
209     earlier in the .thuban file. Look it up self.idmap
210    
211     'ascii' -- The attribute is converted to a bytestring with
212     ascii encoding.
213 bh 1844
214     a callable -- The attribute value is passed to the callable
215 bh 1970 and the return value is used as the converted
216 bh 1844 value
217 bh 1970
218     If no conversion is specified for an attribute it is converted
219     with self.encode.
220 bh 1268 """
221     normalized = {}
222    
223     for d in descr:
224     if d.required and not attrs.has_key(d.fullname):
225 bh 1642 raise LoadError("Element %s requires an attribute %r"
226     % (element, d.name))
227 bh 1268 value = attrs.get(d.fullname, d.default)
228    
229 bh 1646 if d.conversion in ("idref", "shapesource"):
230 bh 1268 if value in self.idmap:
231     value = self.idmap[value]
232     else:
233     raise LoadError("Element %s requires an already defined ID"
234     " in attribute %r"
235     % (element, d.name))
236     elif d.conversion == "table":
237     if value in self.idmap:
238     value = self.idmap[value]
239     if isinstance(value, ShapefileStore):
240     value = value.Table()
241     else:
242     raise LoadError("Element %s requires an already defined ID"
243     " in attribute %r"
244     % (element, d.name))
245     elif d.conversion == "filename":
246     value = os.path.abspath(os.path.join(self.GetDirectory(),
247     value))
248 bh 1646 elif d.conversion == "ascii":
249     value = value.encode("ascii")
250 bh 1844 elif d.conversion:
251     # Assume it's a callable
252     value = d.conversion(value)
253 bh 1970 else:
254     value = self.encode(value)
255 bh 1268
256     normalized[d.name] = value
257     return normalized
258    
259 bh 1646 def start_dbconnection(self, name, qname, attrs):
260     attrs = self.check_attrs(name, attrs,
261     [AttrDesc("id", True),
262     AttrDesc("dbtype", True),
263     AttrDesc("host", False, ""),
264     AttrDesc("port", False, ""),
265     AttrDesc("user", False, ""),
266     AttrDesc("dbname", True)])
267     ID = attrs["id"]
268     dbtype = attrs["dbtype"]
269     if dbtype != "postgis":
270     raise LoadError("dbtype %r not supported" % filetype)
271    
272     del attrs["id"]
273     del attrs["dbtype"]
274    
275     # Try to open the connection and if it fails ask the user for
276     # the correct parameters repeatedly.
277     # FIXME: it would be better not to insist on getting a
278     # connection here. We should handle this more like the raster
279     # images where the layers etc still are created but are not
280     # drawn in case Thuban can't use the data for various reasons
281     while 1:
282     try:
283     conn = postgisdb.PostGISConnection(**attrs)
284     break
285     except postgisdb.ConnectionError, val:
286     if self.db_connection_callback is not None:
287     attrs = self.db_connection_callback(attrs, str(val))
288     if attrs is None:
289     raise LoadCancelled
290     else:
291     raise
292    
293     self.idmap[ID] = conn
294     self.theSession.AddDBConnection(conn)
295    
296     def start_dbshapesource(self, name, qname, attrs):
297     attrs = self.check_attrs(name, attrs,
298     [AttrDesc("id", True),
299     AttrDesc("dbconn", True,
300     conversion = "idref"),
301     AttrDesc("tablename", True,
302     conversion = "ascii")])
303     ID = attrs["id"]
304     db = attrs["dbconn"]
305     tablename = attrs["tablename"]
306     self.idmap[ID] = self.theSession.OpenDBShapeStore(db, tablename)
307    
308 bh 1268 def start_fileshapesource(self, name, qname, attrs):
309     attrs = self.check_attrs(name, attrs,
310     [AttrDesc("id", True),
311     AttrDesc("filename", True,
312     conversion = "filename"),
313     AttrDesc("filetype", True)])
314     ID = attrs["id"]
315     filename = attrs["filename"]
316     filetype = attrs["filetype"]
317     if filetype != "shapefile":
318     raise LoadError("shapesource filetype %r not supported" % filetype)
319     self.idmap[ID] = self.theSession.OpenShapefile(filename)
320    
321     def start_derivedshapesource(self, name, qname, attrs):
322     attrs = self.check_attrs(name, attrs,
323     [AttrDesc("id", True),
324     AttrDesc("shapesource", True,
325     conversion = "shapesource"),
326     AttrDesc("table", True, conversion="table")])
327 bh 1282 store = DerivedShapeStore(attrs["shapesource"], attrs["table"])
328     self.theSession.AddShapeStore(store)
329     self.idmap[attrs["id"]] = store
330 bh 1268
331     def start_filetable(self, name, qname, attrs):
332     attrs = self.check_attrs(name, attrs,
333     [AttrDesc("id", True),
334     AttrDesc("title", True),
335     AttrDesc("filename", True,
336     conversion = "filename"),
337     AttrDesc("filetype")])
338     filetype = attrs["filetype"]
339     if filetype != "DBF":
340     raise LoadError("shapesource filetype %r not supported" % filetype)
341     table = DBFTable(attrs["filename"])
342     table.SetTitle(attrs["title"])
343     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
344    
345     def start_jointable(self, name, qname, attrs):
346     attrs = self.check_attrs(name, attrs,
347     [AttrDesc("id", True),
348     AttrDesc("title", True),
349     AttrDesc("left", True, conversion="table"),
350     AttrDesc("leftcolumn", True),
351     AttrDesc("right", True, conversion="table"),
352 bh 1375 AttrDesc("rightcolumn", True),
353    
354     # jointype is required for file
355     # version 0.9 but this attribute
356     # wasn't in the 0.8 version because of
357     # an oversight so we assume it's
358     # optional since we want to handle
359     # both file format versions here.
360     AttrDesc("jointype", False,
361     default="INNER")])
362    
363     jointype = attrs["jointype"]
364     if jointype == "LEFT OUTER":
365     outer_join = True
366     elif jointype == "INNER":
367     outer_join = False
368     else:
369     raise LoadError("jointype %r not supported" % jointype )
370 bh 1268 table = TransientJoinedTable(self.theSession.TransientDB(),
371     attrs["left"], attrs["leftcolumn"],
372 bh 1375 attrs["right"], attrs["rightcolumn"],
373     outer_join = outer_join)
374 bh 1268 table.SetTitle(attrs["title"])
375     self.idmap[attrs["id"]] = self.theSession.AddTable(table)
376    
377 bh 267 def start_map(self, name, qname, attrs):
378     """Start a map."""
379 frank 1408 self.aMap = Map(self.encode(attrs.get((None, 'title'), None)))
380 bh 267
381     def end_map(self, name, qname):
382     self.theSession.AddMap(self.aMap)
383 jonathan 874 self.aMap = None
384 bh 267
385     def start_projection(self, name, qname, attrs):
386 bh 1844 attrs = self.check_attrs(name, attrs,
387     [AttrDesc("name", conversion=self.encode),
388     AttrDesc("epsg", default=None,
389     conversion=self.encode)])
390     self.projection_name = attrs["name"]
391     self.projection_epsg = attrs["epsg"]
392     self.projection_params = [ ]
393 bh 267
394     def end_projection(self, name, qname):
395 jonathan 874 if self.aLayer is not None:
396     obj = self.aLayer
397     elif self.aMap is not None:
398     obj = self.aMap
399     else:
400     assert False, "projection tag out of context"
401     pass
402    
403 bh 1844 obj.SetProjection(Projection(self.projection_params,
404     self.projection_name,
405     epsg = self.projection_epsg))
406 bh 267
407     def start_parameter(self, name, qname, attrs):
408     s = attrs.get((None, 'value'))
409     s = str(s) # we can't handle unicode in proj
410 bh 1844 self.projection_params.append(s)
411 bh 267
412     def start_layer(self, name, qname, attrs, layer_class = Layer):
413     """Start a layer
414    
415     Instantiate a layer of class layer_class from the attributes in
416     attrs which may be a dictionary as well as the normal SAX attrs
417     object and bind it to self.aLayer.
418     """
419 jonathan 874 title = self.encode(attrs.get((None, 'title'), ""))
420 bh 267 filename = attrs.get((None, 'filename'), "")
421 jonathan 694 filename = os.path.join(self.GetDirectory(), filename)
422 jonathan 930 filename = self.encode(filename)
423     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
424 bh 267 fill = parse_color(attrs.get((None, 'fill'), "None"))
425     stroke = parse_color(attrs.get((None, 'stroke'), "#000000"))
426     stroke_width = int(attrs.get((None, 'stroke_width'), "1"))
427 bh 1268 if attrs.has_key((None, "shapestore")):
428     store = self.idmap[attrs[(None, "shapestore")]]
429     else:
430     store = self.theSession.OpenShapefile(filename)
431     self.aLayer = layer_class(title, store,
432 bh 723 fill = fill, stroke = stroke,
433 jonathan 772 lineWidth = stroke_width,
434 jonathan 930 visible = visible)
435 bh 267
436     def end_layer(self, name, qname):
437     self.aMap.AddLayer(self.aLayer)
438 jonathan 874 self.aLayer = None
439 bh 267
440 jonathan 930 def start_rasterlayer(self, name, qname, attrs, layer_class = RasterLayer):
441     title = self.encode(attrs.get((None, 'title'), ""))
442     filename = attrs.get((None, 'filename'), "")
443     filename = os.path.join(self.GetDirectory(), filename)
444     filename = self.encode(filename)
445     visible = self.encode(attrs.get((None, 'visible'), "true")) != "false"
446    
447     self.aLayer = layer_class(title, filename, visible = visible)
448    
449     def end_rasterlayer(self, name, qname):
450     self.aMap.AddLayer(self.aLayer)
451     self.aLayer = None
452    
453 jonathan 365 def start_classification(self, name, qname, attrs):
454 bh 1970 attrs = self.check_attrs(name, attrs,
455     [AttrDesc("field", True),
456     AttrDesc("field_type", True)])
457     field = attrs["field"]
458     fieldType = attrs["field_type"]
459 jonathan 465
460     dbFieldType = self.aLayer.GetFieldType(field)
461    
462     if fieldType != dbFieldType:
463     raise ValueError(_("xml field type differs from database!"))
464    
465     # setup conversion routines depending on the kind of data
466     # we will be seeing later on
467     if fieldType == FIELDTYPE_STRING:
468     self.conv = str
469     elif fieldType == FIELDTYPE_INT:
470     self.conv = lambda p: int(float(p))
471     elif fieldType == FIELDTYPE_DOUBLE:
472     self.conv = float
473    
474 bh 1452 self.aLayer.SetClassificationColumn(field)
475 jonathan 465
476 jonathan 365 def end_classification(self, name, qname):
477     pass
478    
479     def start_clnull(self, name, qname, attrs):
480 jonathan 439 self.cl_group = ClassGroupDefault()
481 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
482 jonathan 439 self.cl_prop = ClassGroupProperties()
483 jonathan 365
484     def end_clnull(self, name, qname):
485 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
486     self.aLayer.GetClassification().SetDefaultGroup(self.cl_group)
487     del self.cl_group, self.cl_prop
488 jonathan 365
489     def start_clpoint(self, name, qname, attrs):
490     attrib_value = attrs.get((None, 'value'), "0")
491    
492 bh 1452 field = self.aLayer.GetClassificationColumn()
493 jonathan 1428 if self.aLayer.GetFieldType(field) == FIELDTYPE_STRING:
494 bh 1417 value = self.encode(attrib_value)
495     else:
496     value = self.conv(attrib_value)
497 jonathan 439 self.cl_group = ClassGroupSingleton(value)
498 jonathan 874 self.cl_group.SetLabel(self.encode(attrs.get((None, 'label'), "")))
499 jonathan 439 self.cl_prop = ClassGroupProperties()
500 jonathan 413
501 jonathan 365
502     def end_clpoint(self, name, qname):
503 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
504 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
505 jonathan 439 del self.cl_group, self.cl_prop
506 jonathan 365
507     def start_clrange(self, name, qname, attrs):
508    
509 jonathan 874 range = attrs.get((None, 'range'), None)
510     # for backward compatibility (min/max are not saved)
511     min = attrs.get((None, 'min'), None)
512     max = attrs.get((None, 'max'), None)
513    
514 jonathan 365 try:
515 jonathan 874 if range is not None:
516     self.cl_group = ClassGroupRange(Range(range))
517     elif min is not None and max is not None:
518 jonathan 1354 self.cl_group = ClassGroupRange((self.conv(min),
519     self.conv(max)))
520 jonathan 874 else:
521     self.cl_group = ClassGroupRange(Range(None))
522    
523 jonathan 365 except ValueError:
524 jan 374 raise ValueError(_("Classification range is not a number!"))
525 jonathan 365
526 jonathan 439 self.cl_group.SetLabel(attrs.get((None, 'label'), ""))
527     self.cl_prop = ClassGroupProperties()
528 jonathan 413
529 jonathan 365
530     def end_clrange(self, name, qname):
531 jonathan 439 self.cl_group.SetProperties(self.cl_prop)
532 jonathan 614 self.aLayer.GetClassification().AppendGroup(self.cl_group)
533 jonathan 439 del self.cl_group, self.cl_prop
534 jonathan 365
535     def start_cldata(self, name, qname, attrs):
536 jonathan 465 self.cl_prop.SetLineColor(
537     parse_color(attrs.get((None, 'stroke'), "None")))
538     self.cl_prop.SetLineWidth(
539 jonathan 390 int(attrs.get((None, 'stroke_width'), "0")))
540 jonathan 439 self.cl_prop.SetFill(parse_color(attrs.get((None, 'fill'), "None")))
541 jonathan 365
542     def end_cldata(self, name, qname):
543     pass
544    
545 bh 267 def start_labellayer(self, name, qname, attrs):
546     self.aLayer = self.aMap.LabelLayer()
547    
548     def start_label(self, name, qname, attrs):
549     x = float(attrs[(None, 'x')])
550     y = float(attrs[(None, 'y')])
551 jonathan 874 text = self.encode(attrs[(None, 'text')])
552 bh 267 halign = attrs[(None, 'halign')]
553     valign = attrs[(None, 'valign')]
554     self.aLayer.AddLabel(x, y, text, halign = halign, valign = valign)
555    
556     def characters(self, chars):
557     pass
558    
559    
560 bh 1646 def load_session(filename, db_connection_callback = None):
561     """Load a Thuban session from the file object file
562 jonathan 694
563 bh 1646 The db_connection_callback, if given should be a callable object
564     that can be called like this:
565     db_connection_callback(params, message)
566    
567     where params is a dictionary containing the known connection
568     parameters and message is a string with a message why the connection
569     failed. db_connection_callback should return a new dictionary with
570     corrected and perhaps additional parameters like a password or None
571     to indicate that the user cancelled.
572     """
573     handler = SessionLoader(db_connection_callback)
574 jonathan 706 handler.read(filename)
575 jonathan 694
576 bh 6 session = handler.theSession
577     # Newly loaded session aren't modified
578     session.UnsetModified()
579    
580 bh 1930 handler.Destroy()
581    
582 bh 6 return session
583    

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26