Package translate :: Package storage :: Module base
[hide private]
[frames] | no frames]

Source Code for Module translate.storage.base

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright 2006-2009 Zuza Software Foundation 
  5  # 
  6  # This file is part of the Translate Toolkit. 
  7  # 
  8  # This program is free software; you can redistribute it and/or modify 
  9  # it under the terms of the GNU General Public License as published by 
 10  # the Free Software Foundation; either version 2 of the License, or 
 11  # (at your option) any later version. 
 12  # 
 13  # This program is distributed in the hope that it will be useful, 
 14  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 16  # GNU General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with this program; if not, see <http://www.gnu.org/licenses/>. 
 20   
 21  """Base classes for storage interfaces. 
 22   
 23  @organization: Zuza Software Foundation 
 24  @copyright: 2006-2009 Zuza Software Foundation 
 25  @license: U{GPL <http://www.fsf.org/licensing/licenses/gpl.html>} 
 26  """ 
 27   
 28  import logging 
 29  try: 
 30      import cPickle as pickle 
 31  except ImportError: 
 32      import pickle 
 33  from exceptions import NotImplementedError 
 34   
 35  import translate.i18n 
 36  from translate.misc.multistring import multistring 
 37  from translate.misc.typecheck import accepts, Self, IsOneOf 
 38  from translate.storage.placeables import StringElem, general, parse as rich_parse 
 39  from translate.storage.workflow import StateEnum as states 
 40   
 41   
42 -def force_override(method, baseclass):
43 """Forces derived classes to override method.""" 44 45 if type(method.im_self) == type(baseclass): 46 # then this is a classmethod and im_self is the actual class 47 actualclass = method.im_self 48 else: 49 actualclass = method.im_class 50 if actualclass != baseclass: 51 raise NotImplementedError( 52 "%s does not reimplement %s as required by %s" % \ 53 (actualclass.__name__, method.__name__, baseclass.__name__))
54 55
56 -class ParseError(Exception):
57
58 - def __init__(self, inner_exc):
59 self.inner_exc = inner_exc
60
61 - def __str__(self):
62 return repr(self.inner_exc)
63 64
65 -class TranslationUnit(object):
66 """Base class for translation units. 67 68 Our concept of a I{translation unit} is influenced heavily by XLIFF: 69 U{http://www.oasis-open.org/committees/xliff/documents/xliff-specification.htm} 70 71 As such most of the method- and variable names borrows from XLIFF 72 terminology. 73 74 A translation unit consists of the following: 75 - A I{source} string. This is the original translatable text. 76 - A I{target} string. This is the translation of the I{source}. 77 - Zero or more I{notes} on the unit. Notes would typically be some 78 comments from a translator on the unit, or some comments originating 79 from the source code. 80 - Zero or more I{locations}. Locations indicate where in the original 81 source code this unit came from. 82 - Zero or more I{errors}. Some tools (eg. L{pofilter<filters.pofilter>}) 83 can run checks on translations and produce error messages. 84 85 @group Source: *source* 86 @group Target: *target* 87 @group Notes: *note* 88 @group Locations: *location* 89 @group Errors: *error* 90 """ 91 92 rich_parsers = [] 93 """A list of functions to use for parsing a string into a rich string 94 tree.""" 95 96 # State constants 97 S_OBSOLETE = states.OBSOLETE 98 S_EMPTY = states.EMPTY 99 S_NEEDS_WORK = states.NEEDS_WORK 100 S_REJECTED = states.REJECTED 101 S_NEEDS_REVIEW = states.NEEDS_REVIEW 102 S_UNREVIEWED = states.UNREVIEWED 103 S_FINAL = states.FINAL 104 105 # Elaborate state support could look something like this: 106 # STATE = { 107 # S_OBSOLETE: (states.OBSOLETE, states.EMPTY), 108 # S_EMPTY: (states.EMPTY, states.NEEDS_WORK), 109 # S_NEEDS_WORK: (states.NEEDS_WORK, states.REJECTED), 110 # S_REJECTED: (states.REJECTED, states.NEEDS_REVIEW), 111 # S_NEEDS_REVIEW: (states.NEEDS_REVIEW, states.UNREVIEWED), 112 # S_UNREVIEWED: (states.UNREVIEWED, states.FINAL), 113 # S_FINAL: (states.FINAL, states.MAX), 114 # } 115 # """ 116 # Default supported states: 117 # * obsolete: The unit is not to be used. 118 # * empty: The unit has not been translated before. 119 # * needs work: Some translation has been done, but is not complete. 120 # * rejected: The unit has been reviewed, but was rejected. 121 # * needs review: The unit has been translated, but review was requested. 122 # * unreviewed: The unit has been translated, but not reviewed. 123 # * final: The unit is translated, reviewed and accepted. 124 # """ 125 # 126 # ... but by default a format will not support state: 127 STATE = {} 128 129 _store = None 130 _source = None 131 _target = None 132 _rich_source = None 133 _rich_target = None 134 _state_n = 0 135 notes = "" 136
137 - def __init__(self, source=None):
138 """Constructs a TranslationUnit containing the given source string.""" 139 if source is not None: 140 self.source = source
141
142 - def __eq__(self, other):
143 """Compares two TranslationUnits. 144 145 @type other: L{TranslationUnit} 146 @param other: Another L{TranslationUnit} 147 @rtype: Boolean 148 @return: Returns True if the supplied TranslationUnit equals this unit. 149 """ 150 return self.source == other.source and self.target == other.target
151
152 - def __str__(self):
153 """Converts to a string representation that can be parsed back using 154 L{parsestring()}.""" 155 # no point in pickling store object, so let's hide it for a while. 156 store = getattr(self, "_store", None) 157 self._store = None 158 dump = pickle.dumps(self) 159 self._store = store 160 return dump
161
162 - def rich_to_multistring(cls, elem_list):
163 """Convert a "rich" string tree to a C{multistring}: 164 165 >>> from translate.storage.placeables.interfaces import X 166 >>> rich = [StringElem(['foo', X(id='xxx', sub=[' ']), 'bar'])] 167 >>> TranslationUnit.rich_to_multistring(rich) 168 multistring(u'foo bar') 169 """ 170 return multistring([unicode(elem) for elem in elem_list])
171 rich_to_multistring = classmethod(rich_to_multistring) 172
173 - def multistring_to_rich(self, mulstring):
174 """Convert a multistring to a list of "rich" string trees: 175 176 >>> target = multistring([u'foo', u'bar', u'baz']) 177 >>> TranslationUnit.multistring_to_rich(target) 178 [<StringElem([<StringElem([u'foo'])>])>, 179 <StringElem([<StringElem([u'bar'])>])>, 180 <StringElem([<StringElem([u'baz'])>])>] 181 """ 182 if isinstance(mulstring, multistring): 183 return [rich_parse(s, self.rich_parsers) for s in mulstring.strings] 184 return [rich_parse(mulstring, self.rich_parsers)]
185
186 - def setsource(self, source):
187 """Sets the source string to the given value.""" 188 self._rich_source = None 189 self._source = source
190 source = property(lambda self: self._source, setsource) 191
192 - def settarget(self, target):
193 """Sets the target string to the given value.""" 194 self._rich_target = None 195 self._target = target
196 target = property(lambda self: self._target, settarget) 197
198 - def _get_rich_source(self):
199 if self._rich_source is None: 200 self._rich_source = self.multistring_to_rich(self.source) 201 return self._rich_source
202
203 - def _set_rich_source(self, value):
204 if not hasattr(value, '__iter__'): 205 raise ValueError('value must be iterable') 206 if len(value) < 1: 207 raise ValueError('value must have at least one element.') 208 if not isinstance(value[0], StringElem): 209 raise ValueError('value[0] must be of type StringElem.') 210 self._rich_source = list(value) 211 multi = self.rich_to_multistring(value) 212 if self.source != multi: 213 self.source = multi
214 rich_source = property(_get_rich_source, _set_rich_source) 215 """ @see: rich_to_multistring 216 @see: multistring_to_rich""" 217
218 - def _get_rich_target(self):
219 if self._rich_target is None: 220 self._rich_target = self.multistring_to_rich(self.target) 221 return self._rich_target
222
223 - def _set_rich_target(self, value):
224 if not hasattr(value, '__iter__'): 225 raise ValueError('value must be iterable') 226 if len(value) < 1: 227 raise ValueError('value must have at least one element.') 228 if not isinstance(value[0], StringElem): 229 raise ValueError('value[0] must be of type StringElem.') 230 self._rich_target = list(value) 231 self.target = self.rich_to_multistring(value)
232 rich_target = property(_get_rich_target, _set_rich_target) 233 """ @see: rich_to_multistring 234 @see: multistring_to_rich""" 235
236 - def gettargetlen(self):
237 """Returns the length of the target string. 238 239 @note: Plural forms might be combined. 240 @rtype: Integer 241 """ 242 length = len(self.target or "") 243 strings = getattr(self.target, "strings", []) 244 if strings: 245 length += sum([len(pluralform) for pluralform in strings[1:]]) 246 return length
247
248 - def getid(self):
249 """A unique identifier for this unit. 250 251 @rtype: string 252 @return: an identifier for this unit that is unique in the store 253 254 Derived classes should override this in a way that guarantees a unique 255 identifier for each unit in the store. 256 """ 257 return self.source
258
259 - def setid(self, value):
260 """Sets the unique identified for this unit. 261 262 only implemented if format allows ids independant from other 263 unit properties like source or context""" 264 pass
265
266 - def getlocations(self):
267 """A list of source code locations. 268 269 @note: Shouldn't be implemented if the format doesn't support it. 270 @rtype: List 271 """ 272 return []
273
274 - def addlocation(self, location):
275 """Add one location to the list of locations. 276 277 @note: Shouldn't be implemented if the format doesn't support it. 278 """ 279 pass
280
281 - def addlocations(self, location):
282 """Add a location or a list of locations. 283 284 @note: Most classes shouldn't need to implement this, 285 but should rather implement L{addlocation()}. 286 @warning: This method might be removed in future. 287 """ 288 if isinstance(location, list): 289 for item in location: 290 self.addlocation(item) 291 else: 292 self.addlocation(location)
293
294 - def getcontext(self):
295 """Get the message context.""" 296 return ""
297
298 - def setcontext(self, context):
299 """Set the message context""" 300 pass
301
302 - def getnotes(self, origin=None):
303 """Returns all notes about this unit. 304 305 It will probably be freeform text or something reasonable that can be 306 synthesised by the format. 307 It should not include location comments (see L{getlocations()}). 308 """ 309 return getattr(self, "notes", "")
310
311 - def addnote(self, text, origin=None, position="append"):
312 """Adds a note (comment). 313 314 @type text: string 315 @param text: Usually just a sentence or two. 316 @type origin: string 317 @param origin: Specifies who/where the comment comes from. 318 Origin can be one of the following text strings: 319 - 'translator' 320 - 'developer', 'programmer', 'source code' (synonyms) 321 """ 322 if position == "append" and getattr(self, "notes", None): 323 self.notes += '\n' + text 324 else: 325 self.notes = text
326
327 - def removenotes(self):
328 """Remove all the translator's notes.""" 329 self.notes = u''
330
331 - def adderror(self, errorname, errortext):
332 """Adds an error message to this unit. 333 334 @type errorname: string 335 @param errorname: A single word to id the error. 336 @type errortext: string 337 @param errortext: The text describing the error. 338 """ 339 pass
340
341 - def geterrors(self):
342 """Get all error messages. 343 344 @rtype: Dictionary 345 """ 346 return {}
347
348 - def markreviewneeded(self, needsreview=True, explanation=None):
349 """Marks the unit to indicate whether it needs review. 350 351 @keyword needsreview: Defaults to True. 352 @keyword explanation: Adds an optional explanation as a note. 353 """ 354 pass
355
356 - def istranslated(self):
357 """Indicates whether this unit is translated. 358 359 This should be used rather than deducing it from .target, 360 to ensure that other classes can implement more functionality 361 (as XLIFF does). 362 """ 363 return bool(self.target) and not self.isfuzzy()
364
365 - def istranslatable(self):
366 """Indicates whether this unit can be translated. 367 368 This should be used to distinguish real units for translation from 369 header, obsolete, binary or other blank units. 370 """ 371 return bool(self.source)
372
373 - def isfuzzy(self):
374 """Indicates whether this unit is fuzzy.""" 375 return False
376
377 - def markfuzzy(self, value=True):
378 """Marks the unit as fuzzy or not.""" 379 pass
380
381 - def isobsolete(self):
382 """indicate whether a unit is obsolete""" 383 return False
384
385 - def makeobsolete(self):
386 """Make a unit obsolete""" 387 pass
388
389 - def isheader(self):
390 """Indicates whether this unit is a header.""" 391 return False
392
393 - def isreview(self):
394 """Indicates whether this unit needs review.""" 395 return False
396
397 - def isblank(self):
398 """Used to see if this unit has no source or target string. 399 400 @note: This is probably used more to find translatable units, 401 and we might want to move in that direction rather and get rid of this. 402 """ 403 return not (self.source or self.target)
404
405 - def hasplural(self):
406 """Tells whether or not this specific unit has plural strings.""" 407 #TODO: Reconsider 408 return False
409
410 - def getsourcelanguage(self):
411 return self._store.getsourcelanguage()
412
413 - def gettargetlanguage(self):
414 return self._store.gettargetlanguage()
415
416 - def merge(self, otherunit, overwrite=False, comments=True, 417 authoritative=False):
418 """Do basic format agnostic merging.""" 419 if not self.target or overwrite: 420 self.rich_target = otherunit.rich_target
421
422 - def unit_iter(self):
423 """Iterator that only returns this unit.""" 424 yield self
425
426 - def getunits(self):
427 """This unit in a list.""" 428 return [self]
429
430 - def buildfromunit(cls, unit):
431 """Build a native unit from a foreign unit, preserving as much 432 information as possible.""" 433 if type(unit) == cls and hasattr(unit, "copy") and callable(unit.copy): 434 return unit.copy() 435 newunit = cls(unit.source) 436 newunit.target = unit.target 437 newunit.markfuzzy(unit.isfuzzy()) 438 locations = unit.getlocations() 439 if locations: 440 newunit.addlocations(locations) 441 notes = unit.getnotes() 442 if notes: 443 newunit.addnote(notes) 444 return newunit
445 buildfromunit = classmethod(buildfromunit) 446 447 xid = property(lambda self: None, lambda self, value: None) 448 rid = property(lambda self: None, lambda self, value: None) 449
450 - def get_state_id(self, n=None):
451 if n is None: 452 n = self.get_state_n() 453 for state_id, state_range in self.STATE.iteritems(): 454 if state_range[0] <= n < state_range[1]: 455 return state_id 456 if self.STATE: 457 raise ValueError('No state containing value %s' % (n)) 458 else: 459 return n
460
461 - def get_state_n(self):
462 if self.STATE: 463 return self._state_n 464 else: 465 return self.istranslated() and self.S_UNREVIEWED or self.S_EMPTY
466
467 - def set_state_n(self, value):
468 self._state_n = value
469
470 - def infer_state(self):
471 """Empty method that should be overridden in sub-classes to infer the 472 current state(_n) of the unit from its current state.""" 473 pass
474 475
476 -class TranslationStore(object):
477 """Base class for stores for multiple translation units of type 478 UnitClass.""" 479 480 UnitClass = TranslationUnit 481 """The class of units that will be instantiated and used by this class""" 482 Name = "Base translation store" 483 """The human usable name of this store type""" 484 Mimetypes = None 485 """A list of MIME types associated with this store type""" 486 Extensions = None 487 """A list of file extentions associated with this store type""" 488 _binary = False 489 """Indicates whether a file should be accessed as a binary file.""" 490 suggestions_in_format = False 491 """Indicates if format can store suggestions and alternative translation 492 for a unit""" 493 494 sourcelanguage = None 495 targetlanguage = None 496
497 - def __init__(self, unitclass=None):
498 """Constructs a blank TranslationStore.""" 499 self.units = [] 500 if unitclass: 501 self.UnitClass = unitclass
502
503 - def getsourcelanguage(self):
504 """Gets the source language for this store""" 505 return self.sourcelanguage
506
507 - def setsourcelanguage(self, sourcelanguage):
508 """Sets the source language for this store""" 509 self.sourcelanguage = sourcelanguage
510
511 - def gettargetlanguage(self):
512 """Gets the target language for this store""" 513 return self.targetlanguage
514
515 - def settargetlanguage(self, targetlanguage):
516 """Sets the target language for this store""" 517 self.targetlanguage = targetlanguage
518
519 - def getprojectstyle(self):
520 """Gets the project type for this store""" 521 return getattr(self, '_project_style', None)
522
523 - def setprojectstyle(self, project_style):
524 """Sets the project type for this store""" 525 self._project_style = project_style
526
527 - def unit_iter(self):
528 """Iterator over all the units in this store.""" 529 for unit in self.units: 530 yield unit
531
532 - def getunits(self):
533 """Return a list of all units in this store.""" 534 return [unit for unit in self.unit_iter()]
535
536 - def addunit(self, unit):
537 """Appends the given unit to the object's list of units. 538 539 This method should always be used rather than trying to modify the 540 list manually. 541 542 @type unit: L{TranslationUnit} 543 @param unit: The unit that will be added. 544 """ 545 unit._store = self 546 self.units.append(unit)
547
548 - def addsourceunit(self, source):
549 """Adds and returns a new unit with the given source string. 550 551 @rtype: L{TranslationUnit} 552 """ 553 unit = self.UnitClass(source) 554 self.addunit(unit) 555 return unit
556
557 - def findid(self, id):
558 """find unit with matching id by checking id_index""" 559 self.require_index() 560 return self.id_index.get(id, None)
561
562 - def findunit(self, source):
563 """Finds the unit with the given source string. 564 565 @rtype: L{TranslationUnit} or None 566 """ 567 if len(getattr(self, "sourceindex", [])): 568 if source in self.sourceindex: 569 return self.sourceindex[source][0] 570 else: 571 for unit in self.units: 572 if unit.source == source: 573 return unit 574 return None
575
576 - def findunits(self, source):
577 """Finds the units with the given source string. 578 579 @rtype: L{TranslationUnit} or None 580 """ 581 if len(getattr(self, "sourceindex", [])): 582 if source in self.sourceindex: 583 return self.sourceindex[source] 584 else: 585 #FIXME: maybe we should generate index here instead since 586 #we'll scan all units anyway 587 result = [] 588 for unit in self.units: 589 if unit.source == source: 590 result.append(unit) 591 return result 592 return None
593
594 - def translate(self, source):
595 """Returns the translated string for a given source string. 596 597 @rtype: String or None 598 """ 599 unit = self.findunit(source) 600 if unit and unit.target: 601 return unit.target 602 else: 603 return None
604
605 - def remove_unit_from_index(self, unit):
606 """Remove a unit from source and locaton indexes""" 607 608 def remove_unit(source): 609 if source in self.sourceindex: 610 try: 611 self.sourceindex[source].remove(unit) 612 if len(self.sourceindex[source]) == 0: 613 del(self.sourceindex[source]) 614 except ValueError: 615 pass
616 617 if unit.hasplural(): 618 for source in unit.source.strings: 619 remove_unit(source) 620 else: 621 remove_unit(unit.source) 622 623 for location in unit.getlocations(): 624 if location in self.locationindex \ 625 and self.locationindex[location] is not None \ 626 and self.locationindex[location] == unit: 627 del(self.locationindex[location])
628
629 - def add_unit_to_index(self, unit):
630 """Add a unit to source and location idexes""" 631 self.id_index[unit.getid()] = unit 632 633 def insert_unit(source): 634 if not source in self.sourceindex: 635 self.sourceindex[source] = [unit] 636 else: 637 self.sourceindex[source].append(unit)
638 639 if unit.hasplural(): 640 for source in unit.source.strings: 641 insert_unit(source) 642 else: 643 insert_unit(unit.source) 644 645 for location in unit.getlocations(): 646 if location in self.locationindex: 647 # if sources aren't unique, don't use them 648 #FIXME: maybe better store a list of units like sourceindex 649 self.locationindex[location] = None 650 else: 651 self.locationindex[location] = unit 652
653 - def makeindex(self):
654 """Indexes the items in this store. At least .sourceindex should be 655 usefull.""" 656 self.locationindex = {} 657 self.sourceindex = {} 658 self.id_index = {} 659 for index, unit in enumerate(self.units): 660 unit.index = index 661 if not (unit.isheader() or unit.isblank()): 662 self.add_unit_to_index(unit)
663
664 - def require_index(self):
665 """make sure source index exists""" 666 if not hasattr(self, "id_index"): 667 self.makeindex()
668
669 - def getids(self, filename=None):
670 """return a list of unit ids""" 671 self.require_index() 672 return self.id_index.keys()
673
674 - def __getstate__(self):
675 odict = self.__dict__.copy() 676 odict['fileobj'] = None 677 return odict
678
679 - def __setstate__(self, dict):
680 self.__dict__.update(dict) 681 if getattr(self, "filename", False): 682 self.fileobj = open(self.filename)
683
684 - def __str__(self):
685 """Converts to a string representation that can be parsed back using 686 L{parsestring()}.""" 687 # We can't pickle fileobj if it is there, so let's hide it for a while. 688 fileobj = getattr(self, "fileobj", None) 689 self.fileobj = None 690 dump = pickle.dumps(self) 691 self.fileobj = fileobj 692 return dump
693
694 - def isempty(self):
695 """Returns True if the object doesn't contain any translation units.""" 696 if len(self.units) == 0: 697 return True 698 for unit in self.units: 699 if unit.istranslatable(): 700 return False 701 return True
702
703 - def _assignname(self):
704 """Tries to work out what the name of the filesystem file is and 705 assigns it to .filename.""" 706 fileobj = getattr(self, "fileobj", None) 707 if fileobj: 708 filename = getattr(fileobj, "name", 709 getattr(fileobj, "filename", None)) 710 if filename: 711 self.filename = filename
712
713 - def parsestring(cls, storestring):
714 """Converts the string representation back to an object.""" 715 newstore = cls() 716 if storestring: 717 newstore.parse(storestring) 718 return newstore
719 parsestring = classmethod(parsestring) 720
721 - def detect_encoding(self, text, default_encodings=None):
722 if not default_encodings: 723 default_encodings = ['utf-8'] 724 try: 725 import chardet 726 # many false complaints with ellipse (see bug 1825) 727 detected_encoding = chardet.detect(text.replace("…", "")) 728 if detected_encoding['confidence'] < 0.48: 729 detected_encoding = None 730 elif detected_encoding['encoding'] == 'ascii': 731 detected_encoding['encoding'] = 'utf-8' 732 except ImportError: 733 detected_encoding = None 734 735 encodings = [] 736 if self.encoding == 'auto': 737 if detected_encoding and detected_encoding['encoding'] not in encodings: 738 encodings.append(detected_encoding['encoding']) 739 for encoding in default_encodings: 740 if encoding not in encodings: 741 encodings.append(encoding) 742 else: 743 encodings.append(self.encoding) 744 if detected_encoding and detected_encoding['encoding'] != self.encoding: 745 logging.warn("trying to parse %s with encoding: %s but detected encoding is %s (confidence: %s)", 746 self.filename, self.encoding, detected_encoding['encoding'], detected_encoding['confidence']) 747 encodings.append(self.encoding) 748 749 for encoding in encodings: 750 try: 751 r_text = unicode(text, encoding) 752 r_encoding = encoding 753 break 754 except UnicodeDecodeError: 755 r_text = None 756 r_encoding = None 757 if r_encoding == 'ascii': 758 r_encoding = 'utf-8' 759 return r_text, r_encoding
760
761 - def parse(self, data):
762 """parser to process the given source string""" 763 self.units = pickle.loads(data).units
764
765 - def savefile(self, storefile):
766 """Writes the string representation to the given file (or filename).""" 767 storestring = str(self) 768 if isinstance(storefile, basestring): 769 mode = 'w' 770 if self._binary: 771 mode = 'wb' 772 storefile = open(storefile, mode) 773 self.fileobj = storefile 774 self._assignname() 775 storefile.write(storestring) 776 storefile.close()
777
778 - def save(self):
779 """Save to the file that data was originally read from, if 780 available.""" 781 fileobj = getattr(self, "fileobj", None) 782 mode = 'w' 783 if self._binary: 784 mode = 'wb' 785 if not fileobj: 786 filename = getattr(self, "filename", None) 787 if filename: 788 fileobj = file(filename, mode) 789 else: 790 fileobj.close() 791 filename = getattr(fileobj, "name", 792 getattr(fileobj, "filename", None)) 793 if not filename: 794 raise ValueError("No file or filename to save to") 795 fileobj = fileobj.__class__(filename, mode) 796 self.savefile(fileobj)
797
798 - def parsefile(cls, storefile):
799 """Reads the given file (or opens the given filename) and parses back 800 to an object.""" 801 mode = 'r' 802 if cls._binary: 803 mode = 'rb' 804 if isinstance(storefile, basestring): 805 storefile = open(storefile, mode) 806 mode = getattr(storefile, "mode", mode) 807 #For some reason GzipFile returns 1, so we have to test for that here 808 if mode == 1 or "r" in mode: 809 storestring = storefile.read() 810 storefile.close() 811 else: 812 storestring = "" 813 newstore = cls.parsestring(storestring) 814 newstore.fileobj = storefile 815 newstore._assignname() 816 return newstore
817 parsefile = classmethod(parsefile) 818