-
Notifications
You must be signed in to change notification settings - Fork 199
Expand file tree
/
Copy path__init__.py
More file actions
376 lines (291 loc) · 11.9 KB
/
Copy path__init__.py
File metadata and controls
376 lines (291 loc) · 11.9 KB
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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# 关键:替换标准库的阻塞调用为非阻塞版本, 必须在所有其他导入前执行,确保覆盖标准库
from gevent import monkey
monkey.patch_all()
import datetime
import time, random
from copy import deepcopy
import json
from flask import redirect, g, flash, request, session, abort, render_template, redirect, Flask
import logging
from logging.handlers import TimedRotatingFileHandler
import os
from flask_appbuilder import AppBuilder, IndexView, SQLA
from flask_appbuilder.baseviews import expose
from flask_compress import Compress
from flask_migrate import Migrate
from flask_talisman import Talisman
from flask_wtf.csrf import CSRFProtect
from werkzeug.middleware.proxy_fix import ProxyFix
import wtforms_json
from myapp.security import MyappSecurityManager
from myapp.utils.core import pessimistic_connection_handling, setup_cache
from myapp.utils.log import DBEventLogger
import pysnooper
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
wtforms_json.init()
# 在这个文件里面只创建app,不要做view层面的事情。
APP_DIR = os.path.dirname(__file__)
# app = Flask(__name__,static_url_path='/static',static_folder='static',template_folder='templates')
app = Flask(__name__) # ,static_folder='/mnt',static_url_path='/mnt'
app.json.sort_keys=False # 返回字典乱序问题
app.json.ensure_ascii = False # 返回 中文乱码问题
CONFIG_MODULE = os.environ.get("MYAPP_CONFIG", "myapp.config")
app.config.from_object(CONFIG_MODULE)
conf = app.config
if conf.get('DATA_DIR', ''):
if not os.path.exists(conf['DATA_DIR']):
os.makedirs(conf['DATA_DIR'], exist_ok=True)
print(conf.get('SQLALCHEMY_DATABASE_URI', ''))
#################################################################
# Handling manifest file logic at app start
#################################################################
# 依赖js和css的配置文件
# MANIFEST_FILE = APP_DIR + "/static/assets/dist/manifest.json"
MANIFEST_FILE = APP_DIR + "/assets/dist/manifest.json"
manifest = {}
# @pysnooper.snoop()
def parse_manifest_json():
global manifest
try:
with open(MANIFEST_FILE, "r") as f:
# the manifest inclues non-entry files
# we only need entries in templates
full_manifest = json.load(f)
manifest = full_manifest.get("entrypoints", {})
except Exception:
pass
# 获取依赖的js文件地址
# @pysnooper.snoop()
def get_js_manifest_files(filename):
if app.debug:
parse_manifest_json()
entry_files = manifest.get(filename, {})
return entry_files.get("js", [])
# 获取依赖的css文件地址
# @pysnooper.snoop()
def get_css_manifest_files(filename):
if app.debug:
parse_manifest_json()
entry_files = manifest.get(filename, {})
return entry_files.get("css", [])
def get_unloaded_chunks(files, loaded_chunks):
filtered_files = [f for f in files if f not in loaded_chunks]
for f in filtered_files:
loaded_chunks.add(f)
return filtered_files
parse_manifest_json()
# 字典每一个key,例如css_manifest都可以在模板中使用
@app.context_processor
def get_manifest():
return dict(
loaded_chunks=set(),
get_unloaded_chunks=get_unloaded_chunks,
js_manifest=get_js_manifest_files,
css_manifest=get_css_manifest_files,
)
#######################################blueprints##########################
if conf.get("BLUEPRINTS"):
for bp in conf.get("BLUEPRINTS"):
try:
print("Registering blueprint: '{}'".format(bp.name))
app.register_blueprint(bp)
except Exception as e:
print("blueprint registration failed")
logging.exception(e)
if conf.get("SILENCE_FAB"):
logging.getLogger("flask_appbuilder").setLevel(logging.ERROR)
# 获取 kubernetes.client.rest 的日志记录器
logging.getLogger("kubernetes.client.rest").setLevel(logging.WARNING)
# 直接使用全局logging,不针对app设置专门的logging了
# if app.debug:
# app.logger.setLevel(logging.DEBUG) # pylint: disable=no-member
# else:
# # In production mode, add log handler to sys.stderr.
# app.logger.addHandler(logging.StreamHandler()) # pylint: disable=no-member
# app.logger.setLevel(logging.INFO) # pylint: disable=no-member
db = SQLA(app)
if conf.get("WTF_CSRF_ENABLED"):
csrf = CSRFProtect(app)
csrf_exempt_list = conf.get("WTF_CSRF_EXEMPT_LIST", [])
for ex in csrf_exempt_list:
csrf.exempt(ex)
pessimistic_connection_handling(db.engine)
cache = setup_cache(app, conf.get("CACHE_CONFIG"))
migrate = Migrate(app, db, directory=APP_DIR + "/migrations")
# from flask_socketio import SocketIO
#
# message_queue = conf.get('SOCKETIO_MESSAGE_QUEUE','')
# socketio = SocketIO(app,cors_allowed_origins='*', message_queue=message_queue)
# 系统全局性 Logging configuration
logging.basicConfig(format=app.config.get("LOG_FORMAT"))
logging.getLogger().setLevel(app.config.get("LOG_LEVEL") if app.config.get("LOG_LEVEL") else 1)
# 日志输出到文件的配置
if conf.get("ENABLE_TIME_ROTATE"):
logging.getLogger().setLevel(conf.get("TIME_ROTATE_LOG_LEVEL"))
handler = TimedRotatingFileHandler(
conf.get("FILENAME"),
when=conf.get("ROLLOVER"),
interval=conf.get("INTERVAL"),
backupCount=conf.get("BACKUP_COUNT"),
)
logging.getLogger().addHandler(handler)
if conf.get("ENABLE_CORS"):
from flask_cors import CORS
CORS(app, **conf.get("CORS_OPTIONS"))
if conf.get("ENABLE_PROXY_FIX"):
app.wsgi_app = ProxyFix(app.wsgi_app)
if conf.get("ENABLE_CHUNK_ENCODING"):
class ChunkedEncodingFix(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# Setting wsgi.input_terminated tells werkzeug.wsgi to ignore
# content-length and read the stream till the end.
if environ.get("HTTP_TRANSFER_ENCODING", "").lower() == u"chunked":
environ["wsgi.input_terminated"] = True
return self.app(environ, start_response)
app.wsgi_app = ChunkedEncodingFix(app.wsgi_app)
if conf.get("UPLOAD_FOLDER"):
try:
os.makedirs(conf.get("UPLOAD_FOLDER"))
except OSError:
pass
if conf.get("ADDITIONAL_MIDDLEWARE"):
for middleware in conf.get("ADDITIONAL_MIDDLEWARE"):
app.wsgi_app = middleware(app.wsgi_app)
class MyIndexView(IndexView):
@expose("/")
def index(self):
if g.user is None or not g.user.get_id():
return redirect(appbuilder.get_url_for_login)
# return redirect("/myapp/home")
return redirect("/frontend/")
custom_sm = conf.get("CUSTOM_SECURITY_MANAGER") or MyappSecurityManager
if not issubclass(custom_sm, MyappSecurityManager):
raise Exception(
"""Your CUSTOM_SECURITY_MANAGER must now extend MyappSecurityManager,
not FAB's security manager.
See [4565] in UPDATING.md"""
)
# 创建appbuilder
with app.app_context():
# 创建所有表
# db.create_all()
# 创建fab
appbuilder = AppBuilder(
app,
db.session,
base_template="myapp/base.html",
indexview=MyIndexView, # 首页
security_manager_class=custom_sm, # 自定义认证方式
# Run `myapp init` to update FAB's perms,设置为true就可以自动更新了,这样才能自动添加新建权限
update_perms=True,
)
security_manager = appbuilder.sm
results_backend = conf.get("RESULTS_BACKEND")
# Merge user defined feature flags with default feature flags
_feature_flags = conf.get("DEFAULT_FEATURE_FLAGS") or {}
_feature_flags.update(conf.get("FEATURE_FLAGS") or {})
# Event Logger
event_logger = conf.get("EVENT_LOGGER", DBEventLogger)()
def get_feature_flags():
GET_FEATURE_FLAGS_FUNC = conf.get("GET_FEATURE_FLAGS_FUNC")
if GET_FEATURE_FLAGS_FUNC:
return GET_FEATURE_FLAGS_FUNC(deepcopy(_feature_flags))
return _feature_flags
def is_feature_enabled(feature):
"""Utility function for checking whether a feature is turned on"""
return get_feature_flags().get(feature)
# Flask-Compress
if conf.get("ENABLE_FLASK_COMPRESS"):
Compress(app)
if conf.get("TALISMAN_ENABLED"):
talisman_config = conf.get("TALISMAN_CONFIG")
Talisman(app, **talisman_config)
# Hook that provides administrators a handle on the Flask APP
# after initialization
flask_app_mutator = conf.get("FLASK_APP_MUTATOR")
if flask_app_mutator:
flask_app_mutator(app)
import pysnooper
import jwt
# 先经历flask自己的用户识别,再经历这里的before_request,再进行sm里面的before_request,比如load_user_from_header函数
@app.before_request
# @pysnooper.snoop()
def check_login():
# /static下面不少地方静态文件直接访问。所以不能加权限限制
static_urls = ['/static/', '/logout', '/login','/register', '/health', '/wechat','/wework', '/dingtalk','/proxy','/llm/api/','/message_modelview/api/']
for url in static_urls:
if url in request.path:
return
if request.method.lower()=='options':
return
if g.user is None or not g.user.get_id():
# 支持跨域名cookie登录,有平台域名共享时打开
myapp_username = request.cookies.get('myapp_username', '')
if conf.get('AUTH_PLATFORM_ACCESS',False) and myapp_username:
try:
user = security_manager.find_user(myapp_username)
if not user:
abort(401)
else:
g.user = user
return
except Exception as e:
print(e)
# 支持header认证
authorization_value = request.headers.get('Authorization','')
if authorization_value:
try:
user = security_manager.load_user_from_header(authorization_value)
if not user:
abort(401)
else:
g.user = user
return
except Exception as e:
print(e)
abort(401)
# # 判断静态文件只能访问自己的静态文件,这样代码层面的访问就都要加请求header了,比如dataset任务模板
# if '/static/mnt' in request.path and f'/static/mnt/{g.user.username}' not in request.path:
# abort(401)
# 添加每次请求后的操作函数,必须要返回res
@app.after_request
def myapp_after_request(resp):
try:
if g.user and hasattr(g.user,'username') and g.user.username:
resp.set_cookie('myapp_username', g.user.username,domain=conf.get('COOKIE_DOMAIN',None) if conf.get('COOKIE_DOMAIN',None) else None) # 设置用户信息传递
# resp.set_cookie('myapp_username', g.user.username) # 设置用户信息传递
if hasattr(g, 'id'):
resp.set_cookie('id', str(g.id), max_age=3) # 设置有效期
except Exception as e:
print(e)
resp.set_cookie('myapp_username', 'myapp')
# resp.delete_cookie('id')
return resp
# 配置影响后操作
@app.after_request
def apply_http_headers(response):
"""Applies the configuration's http headers to all responses"""
for k, v in conf.get("HTTP_HEADERS").items():
response.headers[k] = v
# response.headers.add("Access-Control-Allow-Origin", "*")
return response
@appbuilder.app.errorhandler(404)
def page_not_found(e):
return (
render_template(
"404.html", base_template=appbuilder.base_template, appbuilder=appbuilder
),
404,
)
# 配置werkzeug的日志级别为error,这样就不会频繁的打印访问路径了。
# log = logging.getLogger('werkzeug').setLevel(logging.ERROR)
# if __name__ != '__main__':
# # 如果不是直接运行,则将日志输出到 gunicorn 中
# gunicorn_logger = logging.getLogger('gunicorn.error')
# app.logger.handlers = gunicorn_logger.handlers
# app.logger.setLevel(gunicorn_logger.level)
# 引入视图
from myapp import views