Skip to content

Commit 7c9323f

Browse files
committed
feat: 添加插件初始化脚本
1 parent f215b0b commit 7c9323f

7 files changed

Lines changed: 299 additions & 9 deletions

File tree

app/app.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ def create_tables(app):
2525
db.create_all()
2626

2727

28-
def create_app():
28+
def create_app(register_all=True):
2929
app = Flask(__name__)
3030
app.config.from_object('app.config.setting')
3131
app.config.from_object('app.config.secure')
32-
register_blueprints(app)
33-
Lin(app)
34-
apply_cors(app)
35-
# 创建所有表格
36-
create_tables(app)
32+
if register_all:
33+
register_blueprints(app)
34+
Lin(app)
35+
apply_cors(app)
36+
# 创建所有表格
37+
create_tables(app)
3738
return app

app/config/setting.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@
1515
# 插件模块暂时没有开启,以下配置可忽略
1616
# plugin config写在字典里面
1717
PLUGIN_PATH = {
18-
'oss': {'path': 'app.plugins.oss', 'enable': True, 'upload_folder': 'app/static'},
19-
'poem': {'path': 'app.plugins.poem', 'enable': True, 'limit': 5},
18+
'poem': {'path': 'app.plugins.poem', 'enable': True, 'version': '0.0.1', 'limit': 20},
19+
'oss': {'path': 'app.plugins.oss', 'enable': True, 'version': '0.0.1', 'access_key_id': 'not complete', 'access_key_secret': 'not complete', 'endpoint': 'http://oss-cn-shenzhen.aliyuncs.com', 'bucket_name': 'not complete', 'upload_folder': 'app', 'allowed_extensions': ['jpg', 'gif', 'png', 'bmp']}
2020
}

app/plugins/poem/app/__init__.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,79 @@
44
"""
55
from .controller import api
66
from .model import Poem
7+
8+
9+
def initial_data():
10+
from app.app import create_app
11+
from lin.db import db
12+
13+
app = create_app()
14+
with app.app_context():
15+
with db.auto_commit():
16+
# 添加诗歌
17+
img_url = 'http://yanlan.oss-cn-shenzhen.aliyuncs.com/gqmgbmu06yO2zHD.png'
18+
poem1 = Poem()
19+
poem1.title = '生查子·元夕'
20+
poem1.author = '欧阳修'
21+
poem1.dynasty = '宋代'
22+
poem1._content = """去年元夜时/花市灯如昼/月上柳梢头/人约黄昏后|今年元夜时/月与灯依旧/不见去年人/泪湿春衫袖"""
23+
poem1.image = img_url
24+
db.session.add(poem1)
25+
26+
poem2 = Poem()
27+
poem2.title = '临江仙·送钱穆父'
28+
poem2.author = '苏轼'
29+
poem2.dynasty = '宋代'
30+
poem2._content = """一别都门三改火/天涯踏尽红尘/依然一笑作春温/无波真古井/有节是秋筠|惆怅孤帆连夜发/送行淡月微云/尊前不用翠眉颦/人生如逆旅/我亦是行人"""
31+
poem2.image = img_url
32+
db.session.add(poem2)
33+
34+
poem3 = Poem()
35+
poem3.title = '春望词四首'
36+
poem3.author = '薛涛'
37+
poem3.dynasty = '唐代'
38+
poem3._content = """花开不同赏/花落不同悲/欲问相思处/花开花落时/揽草结同心/将以遗知音/春愁正断绝/春鸟复哀吟/风花日将老/佳期犹渺渺/不结同心人/空结同心草/那堪花满枝/翻作两相思/玉箸垂朝镜/春风知不知"""
39+
poem3.image = img_url
40+
db.session.add(poem3)
41+
42+
poem4 = Poem()
43+
poem4.title = '长相思'
44+
poem4.author = '纳兰性德'
45+
poem4.dynasty = '清代'
46+
poem4._content = """山一程/水一程/身向榆关那畔行/夜深千帐灯|风一更/雪一更/聒碎乡心梦不成/故园无此声"""
47+
poem4.image = img_url
48+
db.session.add(poem4)
49+
50+
poem5 = Poem()
51+
poem5.title = '离思五首·其四'
52+
poem5.author = '元稹'
53+
poem5.dynasty = '唐代'
54+
poem5._content = """曾经沧海难为水/除却巫山不是云/取次花丛懒回顾/半缘修道半缘君"""
55+
poem5.image = img_url
56+
db.session.add(poem5)
57+
58+
poem6 = Poem()
59+
poem6.title = '浣溪沙'
60+
poem6.author = '晏殊'
61+
poem6.dynasty = '宋代'
62+
poem6._content = """一曲新词酒一杯/去年天气旧亭台/夕阳西下几时回|无可奈何花落去/似曾相识燕归来/小园香径独徘徊"""
63+
poem6.image = img_url
64+
db.session.add(poem6)
65+
66+
poem7 = Poem()
67+
poem7.title = '浣溪沙'
68+
poem7.author = '纳兰性德'
69+
poem7.dynasty = '清代'
70+
poem7._content = """残雪凝辉冷画屏/落梅横笛已三更/更无人处月胧明|我是人间惆怅客/知君何事泪纵横/断肠声里忆平生"""
71+
poem7.image = img_url
72+
db.session.add(poem7)
73+
74+
poem8 = Poem()
75+
poem8.title = '蝶恋花·春景'
76+
poem8.author = '苏轼'
77+
poem8.dynasty = '宋代'
78+
poem8._content = """花褪残红青杏小/燕子飞时/绿水人家绕/枝上柳绵吹又少/天涯何处无芳草|墙里秋千墙外道/墙外行人/墙里佳人笑/笑渐不闻声渐悄/多情却被无情恼"""
79+
poem8.image = img_url
80+
db.session.add(poem8)
81+
82+
return app

app/plugins/poem/app/model.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77

88
class Poem(Base):
9+
__tablename__ = 'lin_poem'
910
id = Column(Integer, primary_key=True, autoincrement=True)
1011
title = Column(String(50), nullable=False, comment='标题')
1112
author = Column(String(50), default='未名', comment='作者')

app/plugins/poem/info.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
__version__ = '0.1.0'
1+
__name__ = 'poem'
2+
__version__ = '0.0.1'
23
__author__ = 'Lin team'

app/plugins/poem/requirements.txt

Whitespace-only changes.

vendor/plugin_init.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
"""
2+
:copyright: © 2019 by the Lin team.
3+
:license: MIT, see LICENSE for more details.
4+
"""
5+
import re
6+
from importlib import import_module
7+
8+
import os
9+
10+
from app.app import create_app
11+
12+
"""
13+
插件初始化流程:
14+
1、输入要初始化的插件名称。(多个用空格隔开,*表示初始化所有)
15+
2、python依赖的自动检测和安装
16+
2、将插件的配置写入到项目setting.py中
17+
3、将model中的模型插入到数据库中
18+
4、如果有需要,将初始数据插入到数据表中
19+
"""
20+
21+
22+
class PluginInit:
23+
# 插件位置默认前缀
24+
plugin_path = 'app.plugins'
25+
26+
def __init__(self, name):
27+
self.app = create_app(register_all=False)
28+
self.name = name.strip()
29+
# 插件相关的路径信息,包含plugin_path(插件路径),plugin_config_path(插件的配置文件路径),plugin_info_path(插件的基本信息路径)
30+
self.path_info = dict()
31+
# 根据name生成path,写入到path_info属性中
32+
self.generate_path()
33+
# 安装依赖
34+
self.auto_install_rely()
35+
# 将插件的配置自动写入setting
36+
self.auto_write_setting()
37+
# 创建数据表,并且向表中插入模型中的一些初始数据
38+
self.create_data()
39+
40+
def generate_path(self):
41+
if self.name == '*':
42+
names = self.__get_all_plugins()
43+
else:
44+
names = self.name.split(" ")
45+
for name in names:
46+
exit('插件名称不能为空,请重试') if self.name == '' else print('正在初始化插件' + name + '...')
47+
self.path_info[name] = {
48+
'plugin_path': self.plugin_path + '.' + name,
49+
'plugin_config_path': self.plugin_path + '.' + name + '.config',
50+
'plugin_info_path': self.plugin_path + '.' + name + '.info'
51+
}
52+
53+
def auto_install_rely(self):
54+
from subprocess import CalledProcessError
55+
for name in self.path_info:
56+
filename = 'requirements.txt'
57+
file_path = self.app.config.root_path + '/plugins/' + name + '/' + filename
58+
success_msg = '安装' + name + '插件的依赖成功'
59+
fail_msg = name + '插件的依赖安装失败,请[手动安装依赖]: http://doc.7yue.pro/'
60+
if os.path.exists(file_path):
61+
if (os.path.getsize(file_path)) == 0:
62+
continue
63+
print('正在安装' + name + '插件的依赖...')
64+
65+
try:
66+
# 使用try except来判断使用pip管理包还是pipenv管理包,首选pipenv
67+
ret = self.__execute_cmd(cmd='pipenv install -r ' + file_path)
68+
69+
if ret:
70+
print(success_msg)
71+
else:
72+
exit(fail_msg)
73+
74+
except CalledProcessError:
75+
try:
76+
ret = self.__execute_cmd(cmd='pip install -r ' + file_path)
77+
78+
if ret:
79+
print(success_msg)
80+
else:
81+
exit(fail_msg)
82+
83+
except Exception as e:
84+
exit((str(e)) + '\n' + fail_msg)
85+
86+
except Exception as e:
87+
exit((str(e)) + '\n' + fail_msg)
88+
89+
def auto_write_setting(self):
90+
print('正在自动写入配置文件...')
91+
setting_text = dict()
92+
for name, val in self.path_info.items():
93+
try:
94+
info_mod = import_module(self.path_info[name]['plugin_info_path'])
95+
except ModuleNotFoundError as e:
96+
raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确')
97+
98+
res = self._generate_setting(name, info_mod)
99+
setting_text[name] = res
100+
101+
# 正则匹配setting.py中的配置文件,将配置文件替换成新的setting_doc
102+
self.__update_setting(new_setting=setting_text)
103+
104+
def create_data(self):
105+
print('正在创建基础数据...')
106+
for name, val in self.path_info.items():
107+
# 调用插件__init__模块中的initial_data方法,创建初始的数据
108+
try:
109+
plugin_module = import_module(self.path_info[name]['plugin_path'] + '.app.__init__')
110+
dir_info = dir(plugin_module)
111+
except ModuleNotFoundError as e:
112+
raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确')
113+
if 'initial_data' in dir_info:
114+
# TODO 解决多次初始化数据重复添加的问题
115+
plugin_module.initial_data()
116+
print('插件初始化成功')
117+
118+
def _generate_setting(self, name, info_mod):
119+
info_mod_dic = info_mod.__dict__
120+
ret = {
121+
'path': self.path_info[name]['plugin_path'],
122+
'enable': True,
123+
'version': info_mod_dic.pop("__version__", '0.0.1') # info_mod_dic.__version__
124+
}
125+
# 向setting_doc中写入插件的配置项
126+
cfg_mod = import_module(self.path_info[name]['plugin_config_path'])
127+
dic = cfg_mod.__dict__
128+
for key in dic.keys():
129+
if not key.startswith('__'):
130+
ret[key] = dic[key]
131+
return ret
132+
133+
def __update_setting(self, new_setting):
134+
# 得到现存的插件配置
135+
old_setting = self.app.config.get('PLUGIN_PATH')
136+
final_setting = self.__cal_setting(new_setting, old_setting)
137+
138+
sub_str = 'PLUGIN_PATH = ' + self.__format_setting(final_setting)
139+
140+
setting_path = self.app.config.root_path + '/config/setting.py'
141+
with open(setting_path, 'r') as f:
142+
content = f.read()
143+
pattern = 'PLUGIN_PATH = \{([\s\S]*)\}+.*?'
144+
result = re.sub(pattern, sub_str, content)
145+
146+
with open(setting_path, 'w+') as f:
147+
f.write(result)
148+
149+
def __get_all_plugins(self):
150+
# 返回所有插件的目录名称
151+
ret = []
152+
path = self.app.config.root_path + '/plugins'
153+
for file in os.listdir(path=path):
154+
file_path = os.path.join(path, file)
155+
if os.path.isdir(file_path):
156+
ret.append(file)
157+
return ret
158+
159+
@classmethod
160+
def __execute_cmd(cls, cmd):
161+
import subprocess
162+
code = subprocess.check_call(cmd, shell=True, stdout=subprocess.PIPE)
163+
if code == 0:
164+
return True
165+
elif code == 1:
166+
return False
167+
168+
@classmethod
169+
def __format_setting(cls, setting):
170+
# 格式化setting字符串
171+
setting_str = str(setting)
172+
ret = setting_str.replace('},', '},\n ').replace('{', '{\n ', 1)
173+
replace_reg = re.compile(r'\}$')
174+
ret = replace_reg.sub('\n}', ret)
175+
return ret
176+
177+
@staticmethod
178+
def __cal_setting(new_setting, old_setting):
179+
# 将新旧的setting合并,返回一个字典
180+
# 1、对比old和new,并且将这两个配置合并
181+
# 2、如果新的存在,旧的不存在,就追加新的;
182+
# 3、如果旧的存在,新的不存在,就保留旧的;
183+
# 4、如果新旧都存在,那么在版本号相同的情况下,保留旧的配置项,否则新的配置覆盖旧的配置。
184+
185+
final_setting = dict()
186+
all_keys = new_setting.keys() | old_setting.keys() # 得到新旧配置的并集
187+
188+
for key in all_keys:
189+
if key not in old_setting.keys():
190+
# 不存在,追加新的
191+
final_setting[key] = new_setting[key]
192+
else:
193+
# 存在,对比版本号,看看是否需要更新 TODO 优化条件判断
194+
if key not in new_setting:
195+
# 新的不存在
196+
final_setting[key] = old_setting[key]
197+
else:
198+
# 新的存在
199+
if new_setting[key]['version'] == old_setting[key]['version']:
200+
# 版本号相同,使用旧的配置
201+
final_setting[key] = old_setting[key]
202+
else:
203+
# 版本号不同,更新配置为新的
204+
final_setting[key] = new_setting[key]
205+
206+
return final_setting
207+
208+
209+
if __name__ == '__main__':
210+
plugin_name = input('请输入要初始化的插件名,如果多个插件请使用空格分隔插件名,输入*表示初始化所有插件:\n')
211+
PluginInit(plugin_name)

0 commit comments

Comments
 (0)