Changeset 11391:6b2507ba9677 in orange


Ignore:
Timestamp:
03/11/13 16:28:15 (14 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

When possible, node properties are now saved as python literal strings.

Location:
Orange/OrangeCanvas
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/application/canvasmain.py

    r11387 r11391  
    881881        try: 
    882882            parse_scheme(new_scheme, open(filename, "rb"), 
    883                          error_handler=errors.append) 
     883                         error_handler=errors.append, 
     884                         allow_pickle_data=True) 
    884885        except Exception: 
    885886            message_critical( 
     
    995996 
    996997        if document.path() and self.check_can_save(document, document.path()): 
    997             curr_scheme.save_to(open(document.path(), "wb")) 
     998            curr_scheme.save_to(open(document.path(), "wb"), 
     999                                pretty=True, pickle_fallback=True) 
     1000 
    9981001            document.setModified(False) 
    9991002            self.add_recent_scheme(curr_scheme.title, document.path()) 
     
    10381041 
    10391042            try: 
    1040                 curr_scheme.save_to(open(filename, "wb")) 
     1043                curr_scheme.save_to(open(filename, "wb"), 
     1044                                    pretty=True, pickle_fallback=True) 
    10411045            except Exception: 
    10421046                log.error("Error saving %r to %r", curr_scheme, filename, 
  • Orange/OrangeCanvas/scheme/readwrite.py

    r11367 r11391  
    88 
    99from collections import defaultdict 
    10  
    11 import cPickle 
    12  
     10from itertools import chain 
     11 
     12import cPickle as pickle 
     13import json 
     14import pprint 
     15 
     16import ast 
    1317from ast import literal_eval 
    1418 
     
    2630class UnknownWidgetDefinition(Exception): 
    2731    pass 
     32 
     33 
     34def string_eval(source): 
     35    """ 
     36    Evaluate a python string literal `source`. Raise ValueError if 
     37    `source` is not a string literal. 
     38 
     39    >>> string_eval("'a string'") 
     40    a string 
     41 
     42    """ 
     43    node = ast.parse(source, "<source>", mode="eval") 
     44    if not isinstance(node.body, ast.Str): 
     45        raise ValueError("%r is not a string literal" % source) 
     46    return node.body.s 
     47 
     48 
     49def tuple_eval(source): 
     50    """ 
     51    Evaluate a python tuple literal `source` where the elements are 
     52    constrained to be int, float or string. Raise ValueError if not 
     53    a tuple literal. 
     54 
     55    >>> tuple_eval("(1, 2, "3")") 
     56    (1, 2, '3') 
     57 
     58    """ 
     59    node = ast.parse(source, "<source>", mode="eval") 
     60 
     61    if not isinstance(node.body, ast.Tuple): 
     62        raise ValueError("%r is not a tuple literal" % source) 
     63 
     64    if not all(isinstance(el, (ast.Str, ast.Num)) 
     65               for el in node.body.elts): 
     66        raise ValueError("Can only contain numbers or strings") 
     67 
     68    return literal_eval(source) 
     69 
     70 
     71def terminal_eval(source): 
     72    """ 
     73    Evaluate a python 'constant' (string, number, None, True, False) 
     74    `source`. Raise ValueError is not a terminal literal. 
     75 
     76    >>> terminal_eval("True") 
     77    True 
     78 
     79    """ 
     80    node = ast.parse(source, "<source>", mode="eval") 
     81 
     82    try: 
     83        return _terminal_value(node.body) 
     84    except ValueError: 
     85        raise 
     86        raise ValueError("%r is not a terminal constant" % source) 
     87 
     88 
     89def _terminal_value(node): 
     90    if isinstance(node, ast.Str): 
     91        return node.s 
     92    elif isinstance(node, ast.Num): 
     93        return node.n 
     94    elif isinstance(node, ast.Name) and \ 
     95            node.id in ["True", "False", "None"]: 
     96        return __builtins__[node.id] 
     97 
     98    raise ValueError("Not a terminal") 
    2899 
    29100 
     
    46117 
    47118 
    48 def parse_scheme(scheme, stream, error_handler=None): 
     119def parse_scheme(scheme, stream, error_handler=None, 
     120                 allow_pickle_data=False): 
    49121    """ 
    50122    Parse a saved scheme from `stream` and populate a `scheme` 
     
    53125    a 'recoverable' error occurs. By default the exception is simply 
    54126    raised. 
     127 
     128    Parameters 
     129    ---------- 
     130    scheme : :class:`.Scheme` 
     131        A scheme instance to populate with the contents of `stream`. 
     132    stream : file-like object 
     133        A file like object opened for reading. 
     134    error_hander : function, optional 
     135        A function to call with an exception instance when a `recoverable` 
     136        error occurs. 
     137    allow_picked_data : bool, optional 
     138        Specifically allow parsing of picked data streams. 
    55139 
    56140    """ 
     
    70154 
    71155    if version == "1.0": 
    72         parse_scheme_v_1_0(doc, scheme, error_handler=error_handler) 
     156        parse_scheme_v_1_0(doc, scheme, error_handler=error_handler, 
     157                           allow_pickle_data=allow_pickle_data) 
    73158        return scheme 
    74159    else: 
    75         parse_scheme_v_2_0(doc, scheme, error_handler=error_handler) 
     160        parse_scheme_v_2_0(doc, scheme, error_handler=error_handler, 
     161                           allow_pickle_data=allow_pickle_data) 
    76162        return scheme 
    77163 
     
    90176 
    91177    if pos is not None: 
    92         pos = literal_eval(pos) 
     178        pos = tuple_eval(pos) 
    93179 
    94180    return SchemeNode(widget_desc, title=title, position=pos) 
    95181 
    96182 
    97 def parse_scheme_v_2_0(etree, scheme, error_handler, widget_registry=None): 
     183def parse_scheme_v_2_0(etree, scheme, error_handler, widget_registry=None, 
     184                       allow_pickle_data=False): 
    98185    """ 
    99186    Parse an `ElementTree` instance. 
     
    165252 
    166253        if "data" in property_el.attrib: 
    167             data = literal_eval(property_el.attrib.get("data")) 
     254            # data string is 'encoded' with 'repr' i.e. unicode and 
     255            # nonprintable characters are \u or \x escaped. 
     256            # Could use 'codecs' module? 
     257            data = string_eval(property_el.attrib.get("data")) 
    168258        else: 
    169259            data = property_el.text 
    170260 
    171261        properties = None 
    172         try: 
    173             if format != "pickle": 
    174                 raise ValueError("Cannot handle %r format" % format) 
    175  
    176             properties = cPickle.loads(data) 
    177         except Exception: 
    178             log.error("Could not load properties for %r.", node.title, 
    179                       exc_info=True) 
     262        if format != "pickle" or allow_pickle_data: 
     263            try: 
     264                properties = loads(data, format) 
     265            except Exception: 
     266                log.error("Could not load properties for %r.", node.title, 
     267                          exc_info=True) 
    180268 
    181269        if properties is not None: 
     
    186274        if annot_el.tag == "text": 
    187275            rect = annot_el.attrib.get("rect", "(0, 0, 20, 20)") 
    188             rect = literal_eval(rect) 
     276            rect = tuple_eval(rect) 
    189277 
    190278            font_family = annot_el.attrib.get("font-family", "").strip() 
     
    195283                font["family"] = font_family 
    196284            if font_size: 
    197                 font["size"] = literal_eval(font_size) 
     285                font["size"] = int(font_size) 
    198286 
    199287            annot = SchemeTextAnnotation(rect, annot_el.text or "", font=font) 
     
    201289            start = annot_el.attrib.get("start", "(0, 0)") 
    202290            end = annot_el.attrib.get("end", "(0, 0)") 
    203             start, end = map(literal_eval, (start, end)) 
     291            start, end = map(tuple_eval, (start, end)) 
    204292 
    205293            color = annot_el.attrib.get("fill", "red") 
     
    217305 
    218306 
    219 def parse_scheme_v_1_0(etree, scheme, error_handler, widget_registry=None): 
     307def parse_scheme_v_1_0(etree, scheme, error_handler, widget_registry=None, 
     308                       allow_pickle_data=False): 
    220309    """ 
    221310    ElementTree Instance of an old .ows scheme format. 
     
    278367    if settings is not None: 
    279368        data = settings.attrib.get("settingsDictionary", None) 
    280         if data: 
     369        if data and allow_pickle_data: 
    281370            try: 
    282371                properties = literal_eval(data) 
     
    288377        if node.title in properties: 
    289378            try: 
    290                 node.properties = cPickle.loads(properties[node.title]) 
     379                node.properties = pickle.loads(properties[node.title]) 
    291380            except Exception: 
    292381                log.error("Could not unpickle properties for the node %r.", 
     
    307396 
    308397 
    309 def scheme_to_etree(scheme): 
    310     """Return an `xml.etree.ElementTree` representation of the `scheme. 
     398def scheme_to_etree(scheme, data_format="literal", pickle_fallback=False): 
     399    """ 
     400    Return an `xml.etree.ElementTree` representation of the `scheme. 
    311401    """ 
    312402    builder = TreeBuilder(element_factory=Element) 
     
    411501        if node.properties: 
    412502            try: 
    413                 data = cPickle.dumps(node.properties) 
     503                data, format = dumps(node.properties, format=data_format, 
     504                                     pickle_fallback=pickle_fallback) 
    414505            except Exception: 
    415506                log.error("Error serializing properties for node %r", 
     
    418509                builder.start("properties", 
    419510                              {"node_id": str(node_ids[node]), 
    420                                "format": "pickle", 
    421 #                               "data": repr(data), 
    422                                }) 
     511                               "format": format}) 
    423512                builder.data(data) 
    424513                builder.end("properties") 
     
    431520 
    432521 
    433 def scheme_to_ows_stream(scheme, stream, pretty=False): 
    434     """Write scheme to a a stream in Orange Scheme .ows (v 2.0) format. 
    435     """ 
    436     tree = scheme_to_etree(scheme) 
     522def scheme_to_ows_stream(scheme, stream, pretty=False, pickle_fallback=False): 
     523    """ 
     524    Write scheme to a a stream in Orange Scheme .ows (v 2.0) format. 
     525 
     526    Parameters 
     527    ---------- 
     528    scheme : :class:`.Scheme` 
     529        A :class:`.Scheme` instance to serialize. 
     530    stream : file-like object 
     531        A file-like object opened for writing. 
     532    pretty : bool, optional 
     533        If `True` the output xml will be pretty printed (indented). 
     534    pickle_fallback : bool, optional 
     535        If `True` allow scheme node properties to be saves using pickle 
     536        protocol if properties cannot be saved using the default 
     537        notation. 
     538 
     539    """ 
     540    tree = scheme_to_etree(scheme, data_format="literal", 
     541                           pickle_fallback=pickle_fallback) 
    437542 
    438543    if pretty: 
     
    473578 
    474579    return indent_(element, level, True) 
     580 
     581 
     582def dumps(obj, format="literal", prettyprint=False, pickle_fallback=False): 
     583    """ 
     584    Serialize `obj` using `format` ('json' or 'literal') and return its 
     585    string representation and the used serialization format ('literal', 
     586    'json' or 'pickle'). 
     587 
     588    If `pickle_fallback` is True and the serialization with `format` 
     589    fails object's pickle representation will be returned 
     590 
     591    """ 
     592    if format == "literal": 
     593        try: 
     594            return (literal_dumps(obj, prettyprint=prettyprint, indent=1), 
     595                    "literal") 
     596        except (ValueError, TypeError) as ex: 
     597            if not pickle_fallback: 
     598                raise 
     599 
     600            log.warning("Could not serialize to a literal string", 
     601                        exc_info=True) 
     602 
     603    elif format == "json": 
     604        try: 
     605            return (json.dumps(obj, indent=1 if prettyprint else None), 
     606                    "json") 
     607        except (ValueError, TypeError): 
     608            if not pickle_fallback: 
     609                raise 
     610 
     611            log.warning("Could not serialize to a json string", 
     612                        exc_info=True) 
     613 
     614    elif format == "pickle": 
     615        return pickle.dumps(obj), "pickle" 
     616 
     617    else: 
     618        raise ValueError("Unsupported format %r" % format) 
     619 
     620    if pickle_fallback: 
     621        log.warning("Using pickle fallback") 
     622        return pickle.dumps(obj), "pickle" 
     623    else: 
     624        raise Exception("Something strange happened.") 
     625 
     626 
     627def loads(string, format): 
     628    if format == "literal": 
     629        return literal_eval(string) 
     630    elif format == "json": 
     631        return json.loads(string) 
     632    elif format == "pickle": 
     633        return pickle.loads(string) 
     634    else: 
     635        raise ValueError("Unknown format") 
     636 
     637 
     638# This is a subset of PyON serialization. 
     639def literal_dumps(obj, prettyprint=False, indent=4): 
     640    """ 
     641    Write obj into a string as a python literal. 
     642    """ 
     643    memo = {} 
     644    NoneType = type(None) 
     645 
     646    def check(obj): 
     647        if type(obj) in [int, long, float, bool, NoneType, unicode, str]: 
     648            return True 
     649 
     650        if id(obj) in memo: 
     651            raise ValueError("{0} is a recursive structure".format(obj)) 
     652 
     653        memo[id(obj)] = obj 
     654 
     655        if type(obj) in [list, tuple]: 
     656            return all(map(check, obj)) 
     657        elif type(obj) is dict: 
     658            return all(map(check, chain(obj.iterkeys(), obj.itervalues()))) 
     659        else: 
     660            raise TypeError("{0} can not be serialized as a python " 
     661                             "literal".format(type(obj))) 
     662 
     663    check(obj) 
     664 
     665    if prettyprint: 
     666        return pprint.pformat(obj, indent=indent) 
     667    else: 
     668        return repr(obj) 
     669 
     670 
     671literal_loads = literal_eval 
  • Orange/OrangeCanvas/scheme/scheme.py

    r11385 r11391  
    562562        assert(not (self.nodes or self.links or self.annotations)) 
    563563 
    564     def save_to(self, stream, pretty=True): 
     564    def save_to(self, stream, pretty=True, pickle_fallback=False): 
    565565        """ 
    566566        Save the scheme as an xml formated file to `stream` 
     567 
     568        See also 
     569        -------- 
     570        .scheme_to_ows_stream 
     571 
    567572        """ 
    568573        if isinstance(stream, basestring): 
    569574            stream = open(stream, "wb") 
    570575 
    571         scheme_to_ows_stream(self, stream, pretty) 
     576        scheme_to_ows_stream(self, stream, pretty, 
     577                             pickle_fallback=pickle_fallback) 
    572578 
    573579    def load_from(self, stream): 
  • Orange/OrangeCanvas/scheme/tests/test_readwrite.py

    r11112 r11391  
    77               SchemeArrowAnnotation, SchemeTextAnnotation 
    88 
     9from .. import readwrite 
    910from ..readwrite import scheme_to_ows_stream, parse_scheme 
    1011 
     
    7374                self.assertEqual(annot1.start_pos, annot2.start_pos) 
    7475                self.assertEqual(annot1.end_pos, annot2.end_pos) 
     76 
     77    def test_safe_evals(self): 
     78        s = readwrite.string_eval(r"'\x00\xff'") 
     79        self.assertEquals(s, chr(0) + chr(255)) 
     80 
     81        with self.assertRaises(ValueError): 
     82            readwrite.string_eval("[1, 2]") 
     83 
     84        t = readwrite.tuple_eval("(1, 2.0, 'a')") 
     85        self.assertEqual(t, (1, 2.0, 'a')) 
     86 
     87        with self.assertRaises(ValueError): 
     88            readwrite.tuple_eval("u'string'") 
     89 
     90        with self.assertRaises(ValueError): 
     91            readwrite.tuple_eval("(1, [1, [2, ]])") 
     92 
     93        self.assertIs(readwrite.terminal_eval("True"), True) 
     94        self.assertIs(readwrite.terminal_eval("False"), False) 
     95        self.assertIs(readwrite.terminal_eval("None"), None) 
     96 
     97        self.assertEqual(readwrite.terminal_eval("42"), 42) 
     98        self.assertEqual(readwrite.terminal_eval("'42'"), '42') 
     99 
     100    def test_literal_dump(self): 
     101        struct = {1: [{(1, 2): ""}], 
     102                  True: 1.0, 
     103                  None: None} 
     104 
     105        s = readwrite.literal_dumps(struct) 
     106        self.assertEqual(readwrite.literal_loads(s), struct) 
     107 
     108        with self.assertRaises(ValueError): 
     109            recur = [1] 
     110            recur.append(recur) 
     111            readwrite.literal_dumps(recur) 
     112 
     113        with self.assertRaises(TypeError): 
     114            readwrite.literal_dumps(self) 
  • Orange/OrangeCanvas/scheme/widgetsscheme.py

    r11297 r11391  
    181181        return changed 
    182182 
    183     def save_to(self, stream): 
     183    def save_to(self, stream, pretty=True, pickle_fallback=False): 
    184184        self.sync_node_properties() 
    185         Scheme.save_to(self, stream) 
     185        Scheme.save_to(self, stream, pretty, pickle_fallback) 
    186186 
    187187    def __on_help_request(self): 
Note: See TracChangeset for help on using the changeset viewer.