File: compatibility.py

package info (click to toggle)
psychopy 2020.2.10%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 44,056 kB
  • sloc: python: 119,649; javascript: 3,022; makefile: 148; sh: 125; xml: 9
file content (170 lines) | stat: -rw-r--r-- 6,889 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import absolute_import, print_function

# from future import standard_library
# standard_library.install_aliases()
from builtins import str
from builtins import object
import codecs
import pickle
import psychopy.data

######### Begin Compatibility Class Definitions #########


class _oldStyleBaseTrialHandler(object):
    """Please excuse these ugly kluges, but in order to unpickle
        psydat pickled trial handlers that were created using the old-style
        (pre python 2.2) class, original classes have to be defined.
    """
    pass


class _oldStyleBaseStairHandler(object):
    """Stubbed compapatibility class for StairHandler"""
    pass


class _oldStyleTrialHandler(object):
    """Stubbed compapatibility class for TrialHandler"""
    pass


class _oldStyleStairHandler(object):
    """Stubbed compapatibility class for StairHandler"""
    pass


class _oldStyleMultiStairHandler(object):
    """Stubbed compapatibility class for MultiStairHandler"""
    pass
######### End Compatibility Class Definitions #########


def _convertToNewStyle(newClass, oldInstance):
    """Converts un-pickled old-style compatibility classes to new-style ones
       by initializing a new-style class and copying the old compatibility
       instance's attributes.
    """
    # if the oldInstance was an ExperimentHandler it wouldn't throw an error
    # related to itself, but to the underlying loops within it. So check if we
    # have that and then do imports on each loop
    if oldInstance.__class__.__name__ == 'ExperimentHandler':
        newHandler = psychopy.data.ExperimentHandler()
        # newClass()  # Init a new new-style object
    else:
        newHandler = newClass([], 0)  # Init a new new-style object
    for thisAttrib in dir(oldInstance):
        # can handle each attribute differently
        if 'instancemethod' in str(type(getattr(oldInstance, thisAttrib))):
            # this is a method
            continue
        elif thisAttrib == '__weakref__':
            continue
        else:
            value = getattr(oldInstance, thisAttrib)
            setattr(newHandler, thisAttrib, value)
    return newHandler


def fromFile(filename):
    """In order to switch experiment handler to the new-style
    (post-python 2.2, circa 2001) classes, this is a proof-of-concept loader
    based on tools.filetools.fromFile that will load psydat files created
    with either new or old style TrialHandlers or StairHandlers.

    Since this is really just an example, it probably [hopefully] won't be
    incorporated into upstream code, but it will work.

    The method will try to load the file using a normal new-style Pickle
    loader; however, if a type error occurs it will temporarily replace the
    new-style class with a stubbed version of the old-style class and will
    then instantiate a fresh new-style class with the original attributes.
    """
    with codecs.open(filename, 'rb') as f:
        try:
            # Try to load the psydat file into the new-style class.
            contents = pickle.load(f)
        except UnicodeDecodeError:
            f.seek(0)  # reset to start of file to try again
            contents = pickle.load(f, encoding='latin1')  # python 2 data files
        except TypeError as e:
            f.seek(0)  # reset to start of file to try again
            # check er as string for one of our handlers
            errStr = "{}".format(e)
            name = ''
            for thisHandler in ['TrialHandler','StairHandler','MultiStairHandler']:
                if thisHandler in errStr:
                    name = thisHandler
            # if error is from something else try to deduce the class
            if not name:
                name = e.args[1].__name__
            # then process accordingly
            if name == 'TrialHandler':
                currentHandler = psychopy.data.TrialHandler
                # Temporarily replace new-style class
                psychopy.data.TrialHandler = _oldStyleTrialHandler
                oldContents = pickle.load(f)
                psychopy.data.TrialHandler = currentHandler
                contents = _convertToNewStyle(psychopy.data.TrialHandler,
                                              oldContents)
            elif name == 'StairHandler':
                currentHandler = psychopy.data.StairHandler
                # Temporarily replace new-style class
                psychopy.data.StairHandler = _oldStyleStairHandler
                oldContents = pickle.load(f)
                psychopy.data.StairHandler = currentHandler
                contents = _convertToNewStyle(
                    psychopy.data.StairHandler, oldContents)
            elif name == '':
                newStair = psychopy.data.StairHandler
                newMulti = psychopy.data.MultiStairHandler
                # Temporarily replace new-style class
                psychopy.data.StairHandler = _oldStyleStairHandler
                # Temporarily replace new-style class
                psychopy.data.MultiStairHandler = _oldStyleMultiStairHandler
                oldContents = pickle.load(f)
                psychopy.data.MultiStairHandler = newMulti
                # Temporarily replace new-style class:
                psychopy.data.StairHandler = newStair
                contents = _convertToNewStyle(
                    psychopy.data.MultiStairHandler, oldContents)
            else:
                raise TypeError("Didn't recognize %s" % name)
    return contents


def checkCompatibility(old, new, prefs=None, fix=True):
    """Check for known compatibility issue between a pair of versions and fix
    automatically if possible. (This facility, and storing the most recently
    run version of the app, was added in version 1.74.00)

    usage::

        ok, msg =  checkCompatibility(old, new, prefs, fix=True)

    prefs is a standard psychopy preferences object. It isn't needed by all
    checks but may be useful.
    This function can be used simply to check for issues by setting fix=False
    """
    if old == new:
        return 1, ""  # no action needed
    if old > new:  # switch them over
        old, new = new, old

    msg = "From %s to %s:" % (old, new)
    warning = False
    if old[0:4] < '1.74':
        msg += ("\n\nThere were many changes in version 1.74.00 that will "
                "break\ncompatibility with older versions. Make sure you read"
                " the changelog carefully\nbefore using this version. Do not "
                "upgrade to this version halfway through an experiment.\n")
        if fix and 'PatchComponent' not in prefs.builder['hiddenComponents']:
            prefs.builder['hiddenComponents'].append('PatchComponent')
        warning = True
    if not warning:
        msg += "\nNo known compatibility issues"

    return (not warning), msg