Sphinx构建Django自定义包文档时get_user_model未被Mock的问题
问题场景
我正在为Django项目开发自定义包,包含两个认证后端模块,代码如下:
Backend 1 模块
from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend UserModel = get_user_model() # ModelBackend = object # 此操作可临时修复导入问题 class AuthBackendOne(ModelBackend): pass
Backend 2 模块
from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend UserModel = get_user_model() # 此处会调用实际Django方法,导致Mock失效 class AuthBackendTwo(ModelBackend): pass
Sphinx配置文件conf.py中的Mock设置:
autodoc_mock_imports = [ "django.conf", "django.contrib.auth" ]
核心问题
执行sphinx-build生成文档时,Backend 2中的get_user_model()未被Mock,会调用真实的Django方法。测试确认问题出在继承ModelBackend时,虽然导入操作正常,但继承行为会触发真实模块的导入逻辑,导致Mock失效。手动将ModelBackend重赋值为object后,构建可成功完成。
测试现象对比
在两个模块中打印get_user_model及其类型(未调用函数),其中cognito对应AuthBackendOne,ldap对应AuthBackendTwo:
- 未重赋值
ModelBackend时:
cognito sources... [ 33%] kala_common.auth.cognito
django.contrib.auth.get_user_model
<class 'django.contrib.auth.get_user_model'>
ldaping sources... [ 40%] kala_common.auth.ldap
<function get_user_model at 0x7f386829f1c0>
<class 'function'>
- 将
ModelBackend重赋值为object时:
cognito sources... [ 33%] kala_common.auth.cognito
django.contrib.auth.get_user_model
<class 'django.contrib.auth.get_user_model'>
ldaping sources... [ 40%] kala_common.auth.ldap
django.contrib.auth.get_user_model
<class 'django.contrib.auth.get_user_model'>
可见未重赋值时,Backend 2中的get_user_model是真实函数类型,而非Mock对象。尝试将ModelBackend单独加入Mock列表,问题仍未解决。
解决方案
1. 精确Mock子模块
调整autodoc_mock_imports,将django.contrib.auth.backends也加入Mock列表,确保完整覆盖ModelBackend所在的子模块:
autodoc_mock_imports = [ "django.conf", "django.contrib.auth", "django.contrib.auth.backends" ]
这种方式能避免继承ModelBackend时触发真实模块的加载逻辑,让Mock生效。
2. 延迟get_user_model()调用
将模块级别的UserModel = get_user_model()移到类的方法内部执行,避免Sphinx构建时触发函数调用:
# Backend 2 模块修改后 from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend class AuthBackendTwo(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): UserModel = get_user_model() # 后续业务逻辑...
3. 用钩子跳过模块级代码
在conf.py中添加autodoc-skip-member钩子,跳过模块级的UserModel变量解析,避免触发get_user_model()调用:
def skip_module_level_usermodel(app, what, name, obj, skip, options): if what == "module" and name == "UserModel": return True return skip def setup(app): app.connect("autodoc-skip-member", skip_module_level_usermodel)
4. 初始化Django环境
如果需要保留模块级代码,可以在conf.py中手动初始化Django基础环境,让Sphinx在合法环境下构建文档:
import django from django.conf import settings if not settings.configured: settings.configure( DEBUG=True, INSTALLED_APPS=[ "django.contrib.auth", "django.contrib.contenttypes", ] ) django.setup()
这种方式无需依赖Mock,直接让Sphinx在正确的Django环境中处理模块代码。
内容的提问来源于stack exchange,提问作者kalaLokia

