summaryrefslogtreecommitdiffstats
path: root/Lib/lib2to3/fixes/fix_urllib.py
blob: d11220c5e18f581db94b840cac8bca9839fca5ba (plain)
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
171
172
173
174
175
176
177
178
179
180
"""Fix changes imports of urllib which are now incompatible.
   This is rather similar to fix_imports, but because of the more
   complex nature of the fixing for urllib, it has its own fixer.
"""
# Author: Nick Edds

# Local imports
from .fix_imports import alternates, FixImports
from .. import fixer_base
from ..fixer_util import Name, Comma, FromImport, Newline, attr_chain

MAPPING = {'urllib':  [
                ('urllib.request',
                    ['URLOpener', 'FancyURLOpener', 'urlretrieve',
                     '_urlopener', 'urlopen', 'urlcleanup',
                     'pathname2url', 'url2pathname']),
                ('urllib.parse',
                    ['quote', 'quote_plus', 'unquote', 'unquote_plus',
                     'urlencode', 'splitattr', 'splithost', 'splitnport',
                     'splitpasswd', 'splitport', 'splitquery', 'splittag',
                     'splittype', 'splituser', 'splitvalue', ]),
                ('urllib.error',
                    ['ContentTooShortError'])],
           'urllib2' : [
                ('urllib.request',
                    ['urlopen', 'install_opener', 'build_opener',
                     'Request', 'OpenerDirector', 'BaseHandler',
                     'HTTPDefaultErrorHandler', 'HTTPRedirectHandler',
                     'HTTPCookieProcessor', 'ProxyHandler',
                     'HTTPPasswordMgr',
                     'HTTPPasswordMgrWithDefaultRealm',
                     'AbstractBasicAuthHandler',
                     'HTTPBasicAuthHandler', 'ProxyBasicAuthHandler',
                     'AbstractDigestAuthHandler',
                     'HTTPDigestAuthHandler', 'ProxyDigestAuthHandler',
                     'HTTPHandler', 'HTTPSHandler', 'FileHandler',
                     'FTPHandler', 'CacheFTPHandler',
                     'UnknownHandler']),
                ('urllib.error',
                    ['URLError', 'HTTPError']),
           ]
}

# Duplicate the url parsing functions for urllib2.
MAPPING["urllib2"].append(MAPPING["urllib"][1])


def build_pattern():
    bare = set()
    for old_module, changes in MAPPING.items():
        for change in changes:
            new_module, members = change
            members = alternates(members)
            yield """import_name< 'import' (module=%r
                                  | dotted_as_names< any* module=%r any* >) >
                  """ % (old_module, old_module)
            yield """import_from< 'from' mod_member=%r 'import'
                       ( member=%s | import_as_name< member=%s 'as' any > |
                         import_as_names< members=any*  >) >
                  """ % (old_module, members, members)
            yield """import_from< 'from' module_star=%r 'import' star='*' >
                  """ % old_module
            yield """import_name< 'import'
                                  dotted_as_name< module_as=%r 'as' any > >
                  """ % old_module
            yield """power< module_dot=%r trailer< '.' member=%s > any* >
                  """ % (old_module, members)


class FixUrllib(FixImports):

    def build_pattern(self):
        return "|".join(build_pattern())

    def transform_import(self, node, results):
        """Transform for the basic import case. Replaces the old
           import name with a comma separated list of its
           replacements.
        """
        import_mod = results.get('module')
        pref = import_mod.prefix

        names = []

        # create a Node list of the replacement modules
        for name in MAPPING[import_mod.value][:-1]:
            names.extend([Name(name[0], prefix=pref), Comma()])
        names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref))
        import_mod.replace(names)

    def transform_member(self, node, results):
        """Transform for imports of specific module elements. Replaces
           the module to be imported from with the appropriate new
           module.
        """
        mod_member = results.get('mod_member')
        pref = mod_member.prefix
        member = results.get('member')

        # Simple case with only a single member being imported
        if member:
            # this may be a list of length one, or just a node
            if isinstance(member, list):
                member = member[0]
            new_name = None
            for change in MAPPING[mod_member.value]:
                if member.value in change[1]:
                    new_name = change[0]
                    break
            if new_name:
                mod_member.replace(Name(new_name, prefix=pref))
            else:
                self.cannot_convert(node,
                                    'This is an invalid module element')

        # Multiple members being imported
        else:
            # a dictionary for replacements, order matters
            modules = []
            mod_dict = {}
            members = results.get('members')
            for member in members:
                member = member.value
                # we only care about the actual members
                if member != ',':
                    for change in MAPPING[mod_member.value]:
                        if member in change[1]:
                            if change[0] in mod_dict:
                                mod_dict[change[0]].append(member)
                            else:
                                mod_dict[change[0]] = [member]
                                modules.append(change[0])

            new_nodes = []
            for module in modules:
                elts = mod_dict[module]
                names = []
                for elt in elts[:-1]:
                    names.extend([Name(elt, prefix=pref), Comma()])
                names.append(Name(elts[-1], prefix=pref))
                new_nodes.append(FromImport(module, names))
            if new_nodes:
                nodes = []
                for new_node in new_nodes[:-1]:
                    nodes.extend([new_node, Newline()])
                nodes.append(new_nodes[-1])
                node.replace(nodes)
            else:
                self.cannot_convert(node, 'All module elements are invalid')

    def transform_dot(self, node, results):
        """Transform for calls to module members in code."""
        module_dot = results.get('module_dot')
        member = results.get('member')
        # this may be a list of length one, or just a node
        if isinstance(member, list):
            member = member[0]
        new_name = None
        for change in MAPPING[module_dot.value]:
            if member.value in change[1]:
                new_name = change[0]
                break
        if new_name:
            module_dot.replace(Name(new_name,
                                    prefix=module_dot.prefix))
        else:
            self.cannot_convert(node, 'This is an invalid module element')

    def transform(self, node, results):
        if results.get('module'):
            self.transform_import(node, results)
        elif results.get('mod_member'):
            self.transform_member(node, results)
        elif results.get('module_dot'):
            self.transform_dot(node, results)
        # Renaming and star imports are not supported for these modules.
        elif results.get('module_star'):
            self.cannot_convert(node, 'Cannot handle star imports.')
        elif results.get('module_as'):
            self.cannot_convert(node, 'This module is now multiple modules')