Home | History | Annotate | Download | only in fixes
      1 """Fix changes imports of urllib which are now incompatible.
      2    This is rather similar to fix_imports, but because of the more
      3    complex nature of the fixing for urllib, it has its own fixer.
      4 """
      5 # Author: Nick Edds
      6 
      7 # Local imports
      8 from lib2to3.fixes.fix_imports import alternates, FixImports
      9 from lib2to3 import fixer_base
     10 from lib2to3.fixer_util import (Name, Comma, FromImport, Newline,
     11                                 find_indentation, Node, syms)
     12 
     13 MAPPING = {"urllib":  [
     14                 ("urllib.request",
     15                     ["URLopener", "FancyURLopener", "urlretrieve",
     16                      "_urlopener", "urlopen", "urlcleanup",
     17                      "pathname2url", "url2pathname"]),
     18                 ("urllib.parse",
     19                     ["quote", "quote_plus", "unquote", "unquote_plus",
     20                      "urlencode", "splitattr", "splithost", "splitnport",
     21                      "splitpasswd", "splitport", "splitquery", "splittag",
     22                      "splittype", "splituser", "splitvalue", ]),
     23                 ("urllib.error",
     24                     ["ContentTooShortError"])],
     25            "urllib2" : [
     26                 ("urllib.request",
     27                     ["urlopen", "install_opener", "build_opener",
     28                      "Request", "OpenerDirector", "BaseHandler",
     29                      "HTTPDefaultErrorHandler", "HTTPRedirectHandler",
     30                      "HTTPCookieProcessor", "ProxyHandler",
     31                      "HTTPPasswordMgr",
     32                      "HTTPPasswordMgrWithDefaultRealm",
     33                      "AbstractBasicAuthHandler",
     34                      "HTTPBasicAuthHandler", "ProxyBasicAuthHandler",
     35                      "AbstractDigestAuthHandler",
     36                      "HTTPDigestAuthHandler", "ProxyDigestAuthHandler",
     37                      "HTTPHandler", "HTTPSHandler", "FileHandler",
     38                      "FTPHandler", "CacheFTPHandler",
     39                      "UnknownHandler"]),
     40                 ("urllib.error",
     41                     ["URLError", "HTTPError"]),
     42            ]
     43 }
     44 
     45 # Duplicate the url parsing functions for urllib2.
     46 MAPPING["urllib2"].append(MAPPING["urllib"][1])
     47 
     48 
     49 def build_pattern():
     50     bare = set()
     51     for old_module, changes in MAPPING.items():
     52         for change in changes:
     53             new_module, members = change
     54             members = alternates(members)
     55             yield """import_name< 'import' (module=%r
     56                                   | dotted_as_names< any* module=%r any* >) >
     57                   """ % (old_module, old_module)
     58             yield """import_from< 'from' mod_member=%r 'import'
     59                        ( member=%s | import_as_name< member=%s 'as' any > |
     60                          import_as_names< members=any*  >) >
     61                   """ % (old_module, members, members)
     62             yield """import_from< 'from' module_star=%r 'import' star='*' >
     63                   """ % old_module
     64             yield """import_name< 'import'
     65                                   dotted_as_name< module_as=%r 'as' any > >
     66                   """ % old_module
     67             # bare_with_attr has a special significance for FixImports.match().
     68             yield """power< bare_with_attr=%r trailer< '.' member=%s > any* >
     69                   """ % (old_module, members)
     70 
     71 
     72 class FixUrllib(FixImports):
     73 
     74     def build_pattern(self):
     75         return "|".join(build_pattern())
     76 
     77     def transform_import(self, node, results):
     78         """Transform for the basic import case. Replaces the old
     79            import name with a comma separated list of its
     80            replacements.
     81         """
     82         import_mod = results.get("module")
     83         pref = import_mod.prefix
     84 
     85         names = []
     86 
     87         # create a Node list of the replacement modules
     88         for name in MAPPING[import_mod.value][:-1]:
     89             names.extend([Name(name[0], prefix=pref), Comma()])
     90         names.append(Name(MAPPING[import_mod.value][-1][0], prefix=pref))
     91         import_mod.replace(names)
     92 
     93     def transform_member(self, node, results):
     94         """Transform for imports of specific module elements. Replaces
     95            the module to be imported from with the appropriate new
     96            module.
     97         """
     98         mod_member = results.get("mod_member")
     99         pref = mod_member.prefix
    100         member = results.get("member")
    101 
    102         # Simple case with only a single member being imported
    103         if member:
    104             # this may be a list of length one, or just a node
    105             if isinstance(member, list):
    106                 member = member[0]
    107             new_name = None
    108             for change in MAPPING[mod_member.value]:
    109                 if member.value in change[1]:
    110                     new_name = change[0]
    111                     break
    112             if new_name:
    113                 mod_member.replace(Name(new_name, prefix=pref))
    114             else:
    115                 self.cannot_convert(node, "This is an invalid module element")
    116 
    117         # Multiple members being imported
    118         else:
    119             # a dictionary for replacements, order matters
    120             modules = []
    121             mod_dict = {}
    122             members = results["members"]
    123             for member in members:
    124                 # we only care about the actual members
    125                 if member.type == syms.import_as_name:
    126                     as_name = member.children[2].value
    127                     member_name = member.children[0].value
    128                 else:
    129                     member_name = member.value
    130                     as_name = None
    131                 if member_name != u",":
    132                     for change in MAPPING[mod_member.value]:
    133                         if member_name in change[1]:
    134                             if change[0] not in mod_dict:
    135                                 modules.append(change[0])
    136                             mod_dict.setdefault(change[0], []).append(member)
    137 
    138             new_nodes = []
    139             indentation = find_indentation(node)
    140             first = True
    141             def handle_name(name, prefix):
    142                 if name.type == syms.import_as_name:
    143                     kids = [Name(name.children[0].value, prefix=prefix),
    144                             name.children[1].clone(),
    145                             name.children[2].clone()]
    146                     return [Node(syms.import_as_name, kids)]
    147                 return [Name(name.value, prefix=prefix)]
    148             for module in modules:
    149                 elts = mod_dict[module]
    150                 names = []
    151                 for elt in elts[:-1]:
    152                     names.extend(handle_name(elt, pref))
    153                     names.append(Comma())
    154                 names.extend(handle_name(elts[-1], pref))
    155                 new = FromImport(module, names)
    156                 if not first or node.parent.prefix.endswith(indentation):
    157                     new.prefix = indentation
    158                 new_nodes.append(new)
    159                 first = False
    160             if new_nodes:
    161                 nodes = []
    162                 for new_node in new_nodes[:-1]:
    163                     nodes.extend([new_node, Newline()])
    164                 nodes.append(new_nodes[-1])
    165                 node.replace(nodes)
    166             else:
    167                 self.cannot_convert(node, "All module elements are invalid")
    168 
    169     def transform_dot(self, node, results):
    170         """Transform for calls to module members in code."""
    171         module_dot = results.get("bare_with_attr")
    172         member = results.get("member")
    173         new_name = None
    174         if isinstance(member, list):
    175             member = member[0]
    176         for change in MAPPING[module_dot.value]:
    177             if member.value in change[1]:
    178                 new_name = change[0]
    179                 break
    180         if new_name:
    181             module_dot.replace(Name(new_name,
    182                                     prefix=module_dot.prefix))
    183         else:
    184             self.cannot_convert(node, "This is an invalid module element")
    185 
    186     def transform(self, node, results):
    187         if results.get("module"):
    188             self.transform_import(node, results)
    189         elif results.get("mod_member"):
    190             self.transform_member(node, results)
    191         elif results.get("bare_with_attr"):
    192             self.transform_dot(node, results)
    193         # Renaming and star imports are not supported for these modules.
    194         elif results.get("module_star"):
    195             self.cannot_convert(node, "Cannot handle star imports.")
    196         elif results.get("module_as"):
    197             self.cannot_convert(node, "This module is now multiple modules")
    198