记对DRF调用配置文件的源码分析

分析django-restful-framework配置文件调用的源码分析

一、背景

软件的启动的起于配置文件的调用,因此对配置文件的源码,运行机制还是需要去熟悉一下,同时通过源码也可以学习大佬们开发出框架时的设计思想是什么样的。


** DRF的setting.py源码分析**

以下是DRF的setting.py中源码

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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
DEFAULTS = {
# Base API policies
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
'DEFAULT_THROTTLE_CLASSES': [],
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
'DEFAULT_VERSIONING_CLASS': None,

# Generic view behavior
'DEFAULT_PAGINATION_CLASS': None,
'DEFAULT_FILTER_BACKENDS': [],

# Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',

# Throttling
'DEFAULT_THROTTLE_RATES': {
'user': None,
'anon': None,
},
'NUM_PROXIES': None,

# Pagination
'PAGE_SIZE': None,

# Filtering
'SEARCH_PARAM': 'search',
'ORDERING_PARAM': 'ordering',

# Versioning
'DEFAULT_VERSION': None,
'ALLOWED_VERSIONS': None,
'VERSION_PARAM': 'version',

# Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None,

# View configuration
'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',

# Exception handling
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
'NON_FIELD_ERRORS_KEY': 'non_field_errors',

# Testing
'TEST_REQUEST_RENDERER_CLASSES': [
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer'
],
'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',

# Hyperlink settings
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
'URL_FIELD_NAME': 'url',

# Input and output formats
'DATE_FORMAT': ISO_8601,
'DATE_INPUT_FORMATS': [ISO_8601],

'DATETIME_FORMAT': ISO_8601,
'DATETIME_INPUT_FORMATS': [ISO_8601],

'TIME_FORMAT': ISO_8601,
'TIME_INPUT_FORMATS': [ISO_8601],

# Encoding
'UNICODE_JSON': True,
'COMPACT_JSON': True,
'STRICT_JSON': True,
'COERCE_DECIMAL_TO_STRING': True,
'UPLOADED_FILES_USE_URL': True,

# Browseable API
'HTML_SELECT_CUTOFF': 1000,
'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",

# Schemas
'SCHEMA_COERCE_PATH_PK': True,
'SCHEMA_COERCE_METHOD_NAMES': {
'retrieve': 'read',
'destroy': 'delete'
},
}


# List of settings that may be in string import notation.
IMPORT_STRINGS = [
'DEFAULT_RENDERER_CLASSES',
'DEFAULT_PARSER_CLASSES',
'DEFAULT_AUTHENTICATION_CLASSES',
'DEFAULT_PERMISSION_CLASSES',
'DEFAULT_THROTTLE_CLASSES',
'DEFAULT_CONTENT_NEGOTIATION_CLASS',
'DEFAULT_METADATA_CLASS',
'DEFAULT_VERSIONING_CLASS',
'DEFAULT_PAGINATION_CLASS',
'DEFAULT_FILTER_BACKENDS',
'DEFAULT_SCHEMA_CLASS',
'EXCEPTION_HANDLER',
'TEST_REQUEST_RENDERER_CLASSES',
'UNAUTHENTICATED_USER',
'UNAUTHENTICATED_TOKEN',
'VIEW_NAME_FUNCTION',
'VIEW_DESCRIPTION_FUNCTION'
]


# List of settings that have been removed
REMOVED_SETTINGS = [
'PAGINATE_BY', 'PAGINATE_BY_PARAM', 'MAX_PAGINATE_BY',
]


def perform_import(val, setting_name):
"""
If the given setting is a string import notation,
then perform the necessary import or imports.
"""
if val is None:
return None
elif isinstance(val, str):
return import_from_string(val, setting_name)
elif isinstance(val, (list, tuple)):
return [import_from_string(item, setting_name) for item in val]
return val


def import_from_string(val, setting_name):
"""
Attempt to import a class from a string representation.
"""
try:
return import_string(val)
except ImportError as e:
msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
raise ImportError(msg)


class APISettings:
"""
A settings object, that allows API settings to be accessed as properties.
For example:

from rest_framework.settings import api_settings
print(api_settings.DEFAULT_RENDERER_CLASSES)

Any setting with string import paths will be automatically resolved
and return the class, rather than the string literal.
"""
def __init__(self, user_settings=None, defaults=None, import_strings=None):
if user_settings:
self._user_settings = self.__check_user_settings(user_settings)
self.defaults = defaults or DEFAULTS # 如果传入自定义的配置,则使用自定义的配置参数
self.import_strings = import_strings or IMPORT_STRINGS
self._cached_attrs = set()

@property
def user_settings(self):
"""获取主settings中的REST_FRAMEWORK字典配置"""
if not hasattr(self, '_user_settings'):
self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
return self._user_settings

def __getattr__(self, attr):
"""在调用属性前,将属性添加到缓存集合中"""
if attr not in self.defaults:
raise AttributeError("Invalid API setting: '%s'" % attr)

try:
# Check if present in user settings
val = self.user_settings[attr]
except KeyError:
# Fall back to defaults
# 回滚属性
val = self.defaults[attr]

# Coerce import strings into classes
if attr in self.import_strings:
# 基于字符串的反射机制导入属性,val为模块名,attr为属性名
val = perform_import(val, attr)

# Cache the result
# 添加到缓存中
self._cached_attrs.add(attr)
setattr(self, attr, val)
return val

def __check_user_settings(self, user_settings):
"""检查REST_FREAMEWORK配置文件,如果存在过期的配置,抛出异常"""
SETTINGS_DOC = "https://www.django-rest-framework.org/api-guide/settings/"
for setting in REMOVED_SETTINGS:
if setting in user_settings:
raise RuntimeError("The '%s' setting has been removed. Please refer to '%s' for available settings." % (setting, SETTINGS_DOC))
return user_settings

def reload(self):
"""重新加载配置文件,清除缓存"""
for attr in self._cached_attrs:
delattr(self, attr)
self._cached_attrs.clear()
if hasattr(self, '_user_settings'):
delattr(self, '_user_settings')


api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)


def reload_api_settings(*args, **kwargs):
"""刷新缓存,用集合的数据结构"""
setting = kwargs['setting']
if setting == 'REST_FRAMEWORK':
api_settings.reload()


# 信号发送,setting改变,回调刷新缓存中的配置文件
setting_changed.connect(reload_api_settings)


说明:

有些说明我已经在源码中标出来了。


大致说一下我的理解:

DRF集成与Django框架,因此它时必须具备结合Django框架,但依然能够对Django框架拓展配置的能力。

① 所以在APISettings中的__init__初始化函数中,提供了user_settings=None, defaults=None, import_strings=None这些参数作为防御可变参数,提供了自定义配置的灵活性。

self.defaults = defaults or DEFAULTS这样的写法,有效的提供了对自定义参数的绑定,如果default被传进来,那么就使用default,如果为None,那么就使用框架所设定的默认初始配置。因为框架本身需要提供一些基本的配置,以满足最基本的要求,然后需要我们根据不同的需求进行重写相关的方法,类,以及覆盖相关的配置文件。

__getattr__方法,使用的还是蛮巧妙地,在调用属性的之前增强属性添加到集合中去。如果我调用了这个一次属性,将属性添加到缓存集合中。同时调用属性基于字符串的反射机制(getattr),利用importlibimport_module方法进行模块的导入。因为配置文件中都是采用字符串的形式配置,所以要利用反射将每个模块赋给val,因此调用属性点获得的属性val不再是字符串了,而是某一个模块。

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)这种直接写在全局的实例化,其实也算是单例模式的一种,其他模块只需调用api_settings这唯一的一个实例。

setting_changed.connect(reload_api_settings)采用了django的信号机制,程序启动时,开启信号监听,当运行时,配置发生了改变,就会触发回调函数,删除该实例中的所有旧属性,以及清空缓存集合。


学一点,记录一点,积少成多,做好自己就足够了。