Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'

报错

Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'

思路:

  • 根据报错,schema.py第30行,我们看到如下方法:
    Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'
  • 由报错信息可知,此方法中MySQLdb即是pymysql,因为,pymysql与MySQLdb的映射,是在app的_init_.py中定义的。
    Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'
  • 所以我们首先找到pymysql源码,pymysql/_init_.py,搜索escape,如下图,可以看到所有包含escape的方法有三个,且都是由converters这个模块中导入的,也与schema.py中的方法相呼应
    Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'
  • 所以我们下一步定位到converters这个模块中去。全局搜索escape,发现匹配数甚多,但是并没有escape这个方法。
    Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'
  • 简单阅读代码可知,在converters这个模块中,上图的escape_item是主要的转义方法,通过encoders字典,进行数据类型和转义方法的映射。
    Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'
    Django迁移报错:AttributeError:module 'pymysql' has no attribute 'escape'
  • 到此,我已基本明确该如何解这个bug,那就是改源码。
  • 直接将schema.py中,报错的第30行:
    -return MySQLdb.escape(value, MySQLdb.converters.conversions)
    改为:
    -return MySQLdb.escape_item(value, MySQLdb.converters.conversions)
  • 重新运行,成功。

小记:

首先我没有了解清楚这个机制到底是如何匹配的,因为在Django的db源码里,sqllite、oracle、postgresql的schema模块中,都没有escape这个方法,甚至没有converter这个模块,所以数据库类型的匹配肯定是在此之前就已经明确了的,然后不同的数据库运行的是不同的逻辑(这点显而易见,理论上也应是如此)。

第二点,在明确是pymysql和db.mysql的配合出问题之后,为什么确定改源码。
理由如下:

  • 虽然不推荐改源码,但是源码也是人写的,而且像这种web->ORM->db的适配,出错在所难免。
  • 以这个bug为例,在源码里明显没有对应的escape方法,而源码里应该被调用的escape_item方法却没有被调用,大概率是方法名字对接失误,找到应该跳转的方法名即可
  • 基于上手的项目来说,我并没有直接采用migrate来操作数据库,而是sqlmigrate,将所有的改动输出为sql语句,再以sql环境操作数据库。这个有两点好处:多一层检查,避免失误;sql原生,更清楚地了解数据库的改动。
  • 在得到需要的sql语句之后,我还是会把源码还原。