ezKit 1.7.7__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ezKit/__init__.py +0 -1
- ezKit/bottle.py +7 -2
- ezKit/bottle_extensions.py +14 -18
- ezKit/cipher.py +11 -10
- ezKit/database.py +109 -73
- ezKit/http.py +21 -22
- ezKit/mongo.py +24 -21
- ezKit/redis.py +19 -16
- ezKit/sendemail.py +1 -10
- ezKit/token.py +11 -12
- ezKit/utils.py +8 -6
- ezKit/xftp.py +2 -2
- {ezKit-1.7.7.dist-info → ezKit-1.8.0.dist-info}/METADATA +1 -1
- ezKit-1.8.0.dist-info/RECORD +17 -0
- ezKit/files.py +0 -348
- ezKit/plots.py +0 -155
- ezKit/qywx.py +0 -173
- ezKit/reports.py +0 -274
- ezKit/zabbix.py +0 -737
- ezKit-1.7.7.dist-info/RECORD +0 -22
- {ezKit-1.7.7.dist-info → ezKit-1.8.0.dist-info}/LICENSE +0 -0
- {ezKit-1.7.7.dist-info → ezKit-1.8.0.dist-info}/WHEEL +0 -0
- {ezKit-1.7.7.dist-info → ezKit-1.8.0.dist-info}/top_level.txt +0 -0
ezKit/sendemail.py
CHANGED
@@ -1,14 +1,5 @@
|
|
1
|
-
"""
|
2
|
-
发送邮件
|
3
|
-
"""
|
1
|
+
"""发送邮件"""
|
4
2
|
# https://stackoverflow.com/questions/882712/sending-html-email-using-python
|
5
|
-
# pylint: disable=E0611
|
6
|
-
# pylint: disable=R0911
|
7
|
-
# pylint: disable=R0912
|
8
|
-
# pylint: disable=R0913
|
9
|
-
# pylint: disable=R0914
|
10
|
-
# pylint: disable=R0915
|
11
|
-
# pylint: disable=R1710
|
12
3
|
import smtplib
|
13
4
|
from email.header import Header
|
14
5
|
from email.mime.image import MIMEImage
|
ezKit/token.py
CHANGED
@@ -4,33 +4,32 @@ from typing import Any
|
|
4
4
|
|
5
5
|
from loguru import logger
|
6
6
|
|
7
|
-
from .
|
8
|
-
from .utils import datetime_now, datetime_offset, datetime_string_to_datetime, datetime_to_string, v_true
|
7
|
+
from . import cipher, utils
|
9
8
|
|
10
9
|
|
11
10
|
def generate_token(key: str = 'Fc0zXCmGKd7tPu6W', timeout: int = 3600, data: Any = None) -> (str | None):
|
12
11
|
try:
|
13
|
-
now = datetime_now()
|
12
|
+
now = utils.datetime_now()
|
14
13
|
|
15
14
|
if now is None:
|
16
15
|
return None
|
17
16
|
|
18
|
-
offset = datetime_offset(now, seconds=+timeout)
|
17
|
+
offset = utils.datetime_offset(now, seconds=+timeout)
|
19
18
|
|
20
19
|
if offset is None:
|
21
20
|
return None
|
22
21
|
|
23
22
|
source = json.dumps(
|
24
23
|
obj={
|
25
|
-
"datetime": datetime_to_string(offset),
|
24
|
+
"datetime": utils.datetime_to_string(offset),
|
26
25
|
"data": data
|
27
26
|
},
|
28
27
|
default=str
|
29
28
|
)
|
30
29
|
|
31
|
-
|
30
|
+
aes_cipher = cipher.AESCipher(key=key, algorithm='sha256')
|
32
31
|
|
33
|
-
return
|
32
|
+
return aes_cipher.encrypt(source)
|
34
33
|
|
35
34
|
except Exception as e:
|
36
35
|
logger.exception(e)
|
@@ -39,19 +38,19 @@ def generate_token(key: str = 'Fc0zXCmGKd7tPu6W', timeout: int = 3600, data: Any
|
|
39
38
|
|
40
39
|
def parsing_token(token_string: str, key: str = 'Fc0zXCmGKd7tPu6W') -> (dict | None):
|
41
40
|
try:
|
42
|
-
if v_true(token_string, str) is False:
|
41
|
+
if utils.v_true(token_string, str) is False:
|
43
42
|
return None
|
44
43
|
|
45
|
-
|
44
|
+
aes_cipher = cipher.AESCipher(key=key, algorithm='sha256')
|
46
45
|
|
47
|
-
target =
|
46
|
+
target = aes_cipher.decrypt(token_string)
|
48
47
|
|
49
48
|
if target is None:
|
50
49
|
return None
|
51
50
|
|
52
51
|
source: dict = json.loads(target)
|
53
52
|
|
54
|
-
source['datetime'] = datetime_string_to_datetime(source['datetime'])
|
53
|
+
source['datetime'] = utils.datetime_string_to_datetime(source['datetime'])
|
55
54
|
|
56
55
|
return source
|
57
56
|
|
@@ -68,7 +67,7 @@ def certify_token(token_string: str, key: str = 'Fc0zXCmGKd7tPu6W') -> bool:
|
|
68
67
|
if result is None:
|
69
68
|
return False
|
70
69
|
|
71
|
-
if result.get('datetime') < datetime_now(): # type: ignore
|
70
|
+
if result.get('datetime') < utils.datetime_now(): # type: ignore
|
72
71
|
return False
|
73
72
|
|
74
73
|
return True
|
ezKit/utils.py
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
"""
|
2
|
-
Python Utils
|
3
|
-
"""
|
1
|
+
"""Utils"""
|
4
2
|
import csv
|
5
3
|
import datetime
|
6
4
|
import hashlib
|
@@ -49,6 +47,10 @@ def v_true(
|
|
49
47
|
List list/tuple/set []/()/{}
|
50
48
|
Dictionary dict {}
|
51
49
|
|
50
|
+
查看变量类型: type(x)
|
51
|
+
|
52
|
+
判断变量类型: isinstance(x, str)
|
53
|
+
|
52
54
|
函数使用 callable(func) 判断
|
53
55
|
"""
|
54
56
|
|
@@ -623,7 +625,7 @@ def dict_remove_key(
|
|
623
625
|
data: dict,
|
624
626
|
key: str,
|
625
627
|
debug: bool = False
|
626
|
-
) ->
|
628
|
+
) -> dict | None:
|
627
629
|
"""dict remove key"""
|
628
630
|
try:
|
629
631
|
data_copy: dict = deepcopy(data)
|
@@ -1466,8 +1468,8 @@ def create_empty_file(
|
|
1466
1468
|
# 创建一个空文件
|
1467
1469
|
if v_true(debug, bool):
|
1468
1470
|
logger.info(f"file: {file}")
|
1469
|
-
# pylint: disable=R1732
|
1470
|
-
open(file,
|
1471
|
+
# pylint: disable=R1732
|
1472
|
+
open(file, "w", encoding="utf-8").close()
|
1471
1473
|
# 返回文件路径
|
1472
1474
|
return file
|
1473
1475
|
except Exception as e:
|
ezKit/xftp.py
CHANGED
@@ -54,7 +54,7 @@ class XFTP:
|
|
54
54
|
logger.success("FTP connect closed")
|
55
55
|
return True
|
56
56
|
|
57
|
-
def get_file_list(self, target='/') ->
|
57
|
+
def get_file_list(self, target='/') -> list[str] | None:
|
58
58
|
"""Get file list"""
|
59
59
|
try:
|
60
60
|
self.chdir_to_remote(target)
|
@@ -63,7 +63,7 @@ class XFTP:
|
|
63
63
|
logger.exception(e)
|
64
64
|
return None
|
65
65
|
|
66
|
-
def get_file_size(self, file, target="/") ->
|
66
|
+
def get_file_size(self, file, target="/") -> int | None:
|
67
67
|
"""Get file size"""
|
68
68
|
try:
|
69
69
|
self.chdir_to_remote(target)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
ezKit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
ezKit/bottle.py,sha256=usKK1wVaZw4_D-4VwMYmOIc8jtz4TrpM30nck59HMFw,180178
|
3
|
+
ezKit/bottle_extensions.py,sha256=LQikCbbYZBAa4AcihvrTvixWHHMY7OjCBsT02PqWMMM,1129
|
4
|
+
ezKit/cipher.py,sha256=uVUzeoYg5ZPJLpqg2wLst-7vfdDGmqWKka9Oyi0WrCA,2883
|
5
|
+
ezKit/database.py,sha256=awTEdD4aqevyJg5LPBHmUPrOKweqzJ2Ezqqx0_xgUDA,6859
|
6
|
+
ezKit/http.py,sha256=cyS18-TW9f7p1OaiJ4nnVsKZT8Ghy-8OewkfYm8yesw,1790
|
7
|
+
ezKit/mongo.py,sha256=P6WTwFRxaaHixJK_PyKlOfPHkeJRxxrNLV77xy5LVBQ,2048
|
8
|
+
ezKit/redis.py,sha256=HVofsLdSBbBHAR-veumsrjTwZQspRDy2FXNR6MDCCXs,1972
|
9
|
+
ezKit/sendemail.py,sha256=Qxu4XQkHRPeX6FSJdzj-MXND9NyKcgHljbafNmy34H0,8243
|
10
|
+
ezKit/token.py,sha256=9CAZhPdXiRiWoOIeWmP0q6L3j1zQAv4YcVWH95Tjefs,1755
|
11
|
+
ezKit/utils.py,sha256=an7joZy_EEpYfN8zBtEWAnhP0YXYfPieabsK_HAxXl4,48921
|
12
|
+
ezKit/xftp.py,sha256=7BABr-gjxZxK2UXoW9XxN76i1HdubeaomYlYMqRurHE,7770
|
13
|
+
ezKit-1.8.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
14
|
+
ezKit-1.8.0.dist-info/METADATA,sha256=CNkyQ01iJgdaiUFUFq2n8aL9MBXtxduFVK6ZBpJrW68,192
|
15
|
+
ezKit-1.8.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
16
|
+
ezKit-1.8.0.dist-info/top_level.txt,sha256=aYLB_1WODsqNTsTFWcKP-BN0KCTKcV-HZJ4zlHkCFw8,6
|
17
|
+
ezKit-1.8.0.dist-info/RECORD,,
|
ezKit/files.py
DELETED
@@ -1,348 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
from copy import deepcopy
|
3
|
-
from socket import inet_aton
|
4
|
-
|
5
|
-
from loguru import logger
|
6
|
-
|
7
|
-
from . import plots, utils
|
8
|
-
|
9
|
-
|
10
|
-
class Files(object):
|
11
|
-
''' 文件 '''
|
12
|
-
|
13
|
-
''' Markdown 和 HTML 文件 '''
|
14
|
-
_markdown_file, _html_file = None, None
|
15
|
-
|
16
|
-
def __init__(self, markdown_file=None, html_file=None):
|
17
|
-
''' Initiation '''
|
18
|
-
self._markdown_file = markdown_file
|
19
|
-
self._html_file = html_file
|
20
|
-
|
21
|
-
'''
|
22
|
-
def multiple_pieces(
|
23
|
-
self,
|
24
|
-
title='',
|
25
|
-
description='',
|
26
|
-
data=[],
|
27
|
-
image={},
|
28
|
-
number_type=None,
|
29
|
-
number_unit=None,
|
30
|
-
number_handling=None,
|
31
|
-
sort_by_ip=False
|
32
|
-
):
|
33
|
-
|
34
|
-
'''
|
35
|
-
|
36
|
-
def unavailable_hosts(self, hosts=[], **kwargs):
|
37
|
-
''' 异常服务器处理 '''
|
38
|
-
|
39
|
-
logger.success('unavailable hosts | start')
|
40
|
-
|
41
|
-
try:
|
42
|
-
|
43
|
-
# 如果存在异常服务器, 则写入文件
|
44
|
-
if hosts != []:
|
45
|
-
with open(self._markdown_file, 'a') as _file:
|
46
|
-
_file.writelines([
|
47
|
-
'\n',
|
48
|
-
'**异常服务器:**\n',
|
49
|
-
'\n',
|
50
|
-
])
|
51
|
-
for _host in hosts:
|
52
|
-
_file.write('- <span style="color: red">{}</span>\n'.format(_host))
|
53
|
-
|
54
|
-
logger.success('unavailable hosts | end')
|
55
|
-
|
56
|
-
return True
|
57
|
-
|
58
|
-
except Exception as e:
|
59
|
-
logger.exception(e)
|
60
|
-
logger.error('unavailable hosts | end')
|
61
|
-
return False
|
62
|
-
|
63
|
-
def single_piece(
|
64
|
-
self,
|
65
|
-
title='',
|
66
|
-
description='',
|
67
|
-
data=[],
|
68
|
-
image={},
|
69
|
-
number_type=None,
|
70
|
-
number_unit=None,
|
71
|
-
number_handling=None,
|
72
|
-
table_header_title='Host',
|
73
|
-
table_header_data='Data',
|
74
|
-
sort_by_ip=False,
|
75
|
-
**kwargs
|
76
|
-
):
|
77
|
-
''' 单条数据 '''
|
78
|
-
|
79
|
-
match True:
|
80
|
-
case True if type(title) != str or title == '':
|
81
|
-
logger.error('ERROR!! title is not string or none')
|
82
|
-
return None
|
83
|
-
case True if type(description) != str or description == '':
|
84
|
-
logger.error('ERROR!! description is not string or none')
|
85
|
-
return None
|
86
|
-
case True if type(data) != list or data == []:
|
87
|
-
logger.error('ERROR!! data is not list or none')
|
88
|
-
return None
|
89
|
-
case True if type(image) != dict or image == 0:
|
90
|
-
logger.error('ERROR!! image is not dictionary or none')
|
91
|
-
return None
|
92
|
-
|
93
|
-
# 拷贝数据
|
94
|
-
_data = deepcopy(data)
|
95
|
-
|
96
|
-
# 根据 IP 排序
|
97
|
-
if sort_by_ip == True:
|
98
|
-
_ips = utils.list_sort([_host['interfaces'][0]['ip'] for _host in _data], key=inet_aton, deduplication=True)
|
99
|
-
_data = [_host for _ip in _ips for _host in _data if _ip == _host['interfaces'][0]['ip']]
|
100
|
-
|
101
|
-
logger.success('{} | start'.format(title))
|
102
|
-
|
103
|
-
try:
|
104
|
-
|
105
|
-
# 初始化变量
|
106
|
-
_data_idx, _data_result = [], []
|
107
|
-
|
108
|
-
for _host in _data:
|
109
|
-
_value = None
|
110
|
-
if _host.get('history') != None:
|
111
|
-
match True:
|
112
|
-
case True if number_type == 'int':
|
113
|
-
_value = int(_host['history']['value'])
|
114
|
-
case True if number_type == 'float':
|
115
|
-
_value = float(_host['history']['value'])
|
116
|
-
case _:
|
117
|
-
_value = _host['history']['value']
|
118
|
-
_data_idx.append(_host['name'])
|
119
|
-
_data_result.append(number_handling(_value) if callable(number_handling) == True else _value)
|
120
|
-
|
121
|
-
# 判断结果
|
122
|
-
if (len(_data_idx) > 0) and (image != {}):
|
123
|
-
|
124
|
-
with open(self._markdown_file, 'a') as _file:
|
125
|
-
|
126
|
-
_file.writelines([
|
127
|
-
'\n',
|
128
|
-
'## {}\n'.format(title),
|
129
|
-
'\n',
|
130
|
-
'{}\n'.format(description),
|
131
|
-
'\n',
|
132
|
-
'| {} | {} |\n'.format(table_header_title, table_header_data),
|
133
|
-
'| --- | --: |\n'
|
134
|
-
])
|
135
|
-
|
136
|
-
for _i, _v in enumerate(_data_idx):
|
137
|
-
_file.write('| {0} | {1:.2f}{2} |\n'.format(_v, _data_result[_i], number_unit))
|
138
|
-
|
139
|
-
_plot_image = {
|
140
|
-
'title': title,
|
141
|
-
'kind': image['kind'],
|
142
|
-
'size': (20, (len(_data_idx) / 5) * 2),
|
143
|
-
'path': image['path'],
|
144
|
-
'dpi': 300,
|
145
|
-
'width': 0.8
|
146
|
-
}
|
147
|
-
|
148
|
-
_plot_result = plots.bar({image['label']: _data_result}, _data_idx, _plot_image)
|
149
|
-
|
150
|
-
# '<img src="cid:{}">\n'.format(_image['cid'])
|
151
|
-
# '\n'.format(_image['cid'])
|
152
|
-
# 图片显示大小: 仅设置 width, height 自适应
|
153
|
-
if _plot_result == True:
|
154
|
-
_file.writelines([
|
155
|
-
'\n',
|
156
|
-
'<img src="cid:{}" width="1000px">\n'.format(image['cid'])
|
157
|
-
])
|
158
|
-
|
159
|
-
logger.success('{} | end'.format(title))
|
160
|
-
|
161
|
-
return True
|
162
|
-
|
163
|
-
except Exception as e:
|
164
|
-
logger.exception(e)
|
165
|
-
logger.error('{} | end'.format(title))
|
166
|
-
return False
|
167
|
-
|
168
|
-
def multiple_pieces(
|
169
|
-
self,
|
170
|
-
title='',
|
171
|
-
description='',
|
172
|
-
data=[],
|
173
|
-
image={},
|
174
|
-
number_type=None,
|
175
|
-
number_unit=None,
|
176
|
-
number_handling=None,
|
177
|
-
table_header_title='Host',
|
178
|
-
sort_by_ip=False,
|
179
|
-
**kwargs
|
180
|
-
):
|
181
|
-
''' 多条数据 '''
|
182
|
-
|
183
|
-
match True:
|
184
|
-
case True if type(title) != str or title == '':
|
185
|
-
logger.error('ERROR!! title is not string or none')
|
186
|
-
return None
|
187
|
-
case True if type(description) != str or description == '':
|
188
|
-
logger.error('ERROR!! description is not string or none')
|
189
|
-
return None
|
190
|
-
case True if type(data) != list or data == []:
|
191
|
-
logger.error('ERROR!! data is not list or none')
|
192
|
-
return None
|
193
|
-
case True if type(image) != dict or image == 0:
|
194
|
-
logger.error('ERROR!! image is not dictionary or none')
|
195
|
-
return None
|
196
|
-
|
197
|
-
# 拷贝数据
|
198
|
-
_data = deepcopy(data)
|
199
|
-
|
200
|
-
# 根据 IP 排序
|
201
|
-
if sort_by_ip == True:
|
202
|
-
_ips = utils.list_sort([_host['interfaces'][0]['ip'] for _host in _data], key=inet_aton, deduplication=True)
|
203
|
-
_data = [_host for _ip in _ips for _host in _data if _ip == _host['interfaces'][0]['ip']]
|
204
|
-
|
205
|
-
logger.success('{} | start'.format(title))
|
206
|
-
|
207
|
-
try:
|
208
|
-
|
209
|
-
# 初始化变量 (用于 pandas 处理数据)
|
210
|
-
_data_idx, _data_max, _data_avg, _data_min = [], [], [], []
|
211
|
-
|
212
|
-
# 提取数据
|
213
|
-
for _host in _data:
|
214
|
-
_num_max, _num_avg, _num_min = utils.mam_of_numbers([_x['value'] for _x in _host['history']], number_type)
|
215
|
-
if (_num_max != None) and (_num_avg != None) and (_num_min != None):
|
216
|
-
_data_idx.append(_host['name'])
|
217
|
-
_data_max.append(number_handling(_num_max) if callable(number_handling) == True else _num_max)
|
218
|
-
_data_avg.append(number_handling(_num_avg) if callable(number_handling) == True else _num_avg)
|
219
|
-
_data_min.append(number_handling(_num_min) if callable(number_handling) == True else _num_min)
|
220
|
-
|
221
|
-
# 判断结果
|
222
|
-
if (len(_data_idx) > 0) and (image != None):
|
223
|
-
|
224
|
-
# 写入文件
|
225
|
-
with open(self._markdown_file, 'a') as _file:
|
226
|
-
|
227
|
-
_file.writelines([
|
228
|
-
'\n',
|
229
|
-
'## {}\n'.format(title),
|
230
|
-
'\n',
|
231
|
-
'{}\n'.format(description),
|
232
|
-
'\n',
|
233
|
-
'| {} | Min | Average | Max |\n'.format(table_header_title),
|
234
|
-
'| --- | --: | --: | --: |\n'
|
235
|
-
])
|
236
|
-
|
237
|
-
for _i, _v in enumerate(_data_idx):
|
238
|
-
_file.write('| {0} | {1:.2f}{4} | {2:.2f}{4} | {3:.2f}{4} |\n'.format(_v, _data_min[_i], _data_avg[_i], _data_max[_i], number_unit))
|
239
|
-
|
240
|
-
_plot_data = [
|
241
|
-
{'key': 'max', 'label': 'Max', 'kind': 'barh', 'data': _data_max, 'color': '#E74C3C', 'width': 0.8},
|
242
|
-
{'key': 'avg', 'label': 'Avg', 'kind': 'barh', 'data': _data_avg, 'color': '#3498DB', 'width': 0.8},
|
243
|
-
{'key': 'min', 'label': 'Min', 'kind': 'barh', 'data': _data_min, 'color': '#2ECC71', 'width': 0.8}
|
244
|
-
]
|
245
|
-
|
246
|
-
_plot_image = {
|
247
|
-
'title': title,
|
248
|
-
'size': (20, (len(_data_idx) / 5) * 2),
|
249
|
-
'path': image['path'],
|
250
|
-
'dpi': 300
|
251
|
-
}
|
252
|
-
|
253
|
-
_plot_result = plots.bar_cover(_plot_data, _data_idx, _plot_image)
|
254
|
-
|
255
|
-
# '<img src="cid:{}">\n'.format(_image['cid'])
|
256
|
-
# '\n'.format(_image['cid'])
|
257
|
-
# 图片显示大小: 仅设置 width, height 自适应
|
258
|
-
if _plot_result == True:
|
259
|
-
_file.writelines([
|
260
|
-
'\n',
|
261
|
-
'<img src="cid:{}" width="1000px">\n'.format(image['cid'])
|
262
|
-
])
|
263
|
-
|
264
|
-
logger.success('{} | end'.format(title))
|
265
|
-
|
266
|
-
return True
|
267
|
-
|
268
|
-
except Exception as e:
|
269
|
-
logger.exception(e)
|
270
|
-
logger.error('{} | end'.format(title))
|
271
|
-
return False
|
272
|
-
|
273
|
-
def handling_html(self, *args, **kwargs):
|
274
|
-
''' 处理 HTML '''
|
275
|
-
|
276
|
-
logger.success('handling HTML | start')
|
277
|
-
|
278
|
-
try:
|
279
|
-
|
280
|
-
if utils.check_file_type(self._html_file, 'file'):
|
281
|
-
|
282
|
-
# HTML内容
|
283
|
-
_html_lines = None
|
284
|
-
|
285
|
-
# 移除自带样式
|
286
|
-
with open(self._html_file, "r") as _html_input:
|
287
|
-
_html_lines = _html_input.readlines()
|
288
|
-
del _html_lines[7:161]
|
289
|
-
|
290
|
-
# 遍历内容
|
291
|
-
for _i, _v in enumerate(_html_lines):
|
292
|
-
|
293
|
-
# 移除表格宽度
|
294
|
-
_table_width = re.compile(r'<table style="width:100%;">')
|
295
|
-
if len(_table_width.findall(_v)) > 0:
|
296
|
-
_html_lines[_i] = re.sub(_table_width, '<table>', _v)
|
297
|
-
|
298
|
-
# 移除 colgroup
|
299
|
-
_colgroup = re.compile(r'</?colgroup>')
|
300
|
-
if len(_colgroup.findall(_v)) > 0:
|
301
|
-
_html_lines[_i] = re.sub(_colgroup, '', _v)
|
302
|
-
|
303
|
-
# 移除列宽度
|
304
|
-
_col_width = re.compile(r'<col style="width: .*%" />')
|
305
|
-
if len(_col_width.findall(_v)) > 0:
|
306
|
-
_html_lines[_i] = re.sub(_col_width, '', _v)
|
307
|
-
|
308
|
-
# 写入文件
|
309
|
-
with open(self._html_file, "w") as _html_output:
|
310
|
-
_html_output.writelines(_html_lines)
|
311
|
-
|
312
|
-
logger.success('handling HTML | end')
|
313
|
-
return True
|
314
|
-
|
315
|
-
except Exception as e:
|
316
|
-
logger.exception(e)
|
317
|
-
logger.error('handling HTML | end')
|
318
|
-
return False
|
319
|
-
|
320
|
-
def markdown_to_html(self, dir='.', **kwargs):
|
321
|
-
'''
|
322
|
-
Markdown to HTML
|
323
|
-
使用 MacDown 生成 HTML, 然后提取样式到 markdown.html
|
324
|
-
pandoc 生成的 HTML 默认 max-width: 36em, 如果表格内容很长, 会导致表格样式难看
|
325
|
-
所以在 markdown.html 的 body{...} 中添加配置 max-width: unset, 解决内容过长的样式问题
|
326
|
-
所有 a 标签添加 text-decoration: none; 去除链接下划线
|
327
|
-
pandoc --no-highlight -s --quiet -f markdown -t html -H markdown.html -o history.html history.md
|
328
|
-
'''
|
329
|
-
|
330
|
-
logger.success('markdown to html | start')
|
331
|
-
|
332
|
-
try:
|
333
|
-
|
334
|
-
_result = utils.shell(
|
335
|
-
'pandoc --no-highlight -s --quiet -f markdown -t html -H {}/markdown.html -o {} {}'.format(dir, self._html_file, self._markdown_file)
|
336
|
-
)
|
337
|
-
|
338
|
-
if _result != None and _result.returncode == 0:
|
339
|
-
logger.success('markdown to html | end')
|
340
|
-
return True
|
341
|
-
else:
|
342
|
-
logger.error('markdown to html | end')
|
343
|
-
return False
|
344
|
-
|
345
|
-
except Exception as e:
|
346
|
-
logger.exception(e)
|
347
|
-
logger.error('markdown to html | end')
|
348
|
-
return False
|
ezKit/plots.py
DELETED
@@ -1,155 +0,0 @@
|
|
1
|
-
import matplotlib.patches as mpatches
|
2
|
-
import matplotlib.pyplot as plt
|
3
|
-
import numpy as np
|
4
|
-
import pandas as pd
|
5
|
-
from loguru import logger
|
6
|
-
|
7
|
-
|
8
|
-
def bar(data={}, index=[], image={}, **kwargs):
|
9
|
-
'''
|
10
|
-
标准条形图
|
11
|
-
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html
|
12
|
-
https://matplotlib.org/stable/api/pyplot_summary.html
|
13
|
-
'''
|
14
|
-
try:
|
15
|
-
_df = pd.DataFrame(data, index=index)
|
16
|
-
'''
|
17
|
-
title: 标题
|
18
|
-
kind: 类型
|
19
|
-
figsize: 图片大小, 以 100 为基数, 这里输出的图片为 2000 x 2000
|
20
|
-
width: 图形条宽度
|
21
|
-
dpi: 清晰度
|
22
|
-
bbox_inches='tight' 去除图片周边空白
|
23
|
-
'''
|
24
|
-
_ax = _df.plot(title=image.get('title'), kind=image.get('kind', 'bar'), figsize=image.get('size', (10, 10)), width=image.get('width', 0.8))
|
25
|
-
_ax.set_xlim(image.get('xlim'))
|
26
|
-
_ax.set_ylim(image.get('ylim'))
|
27
|
-
_ax.figure.savefig(image.get('path', 'image.png'), dpi=image.get('dpi', 300), bbox_inches='tight')
|
28
|
-
return True
|
29
|
-
|
30
|
-
except Exception as e:
|
31
|
-
logger.exception(e)
|
32
|
-
return False
|
33
|
-
|
34
|
-
def bar_cover(data=[], index=[], image={}, **kwargs):
|
35
|
-
'''
|
36
|
-
重叠条形图
|
37
|
-
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html
|
38
|
-
https://matplotlib.org/stable/api/pyplot_summary.html
|
39
|
-
df.plot.bar() 和 df.plot.barh() 中 stacked=True 的效果是叠加, 即所有图层堆积拼接在一起
|
40
|
-
这里需要的效果是图层重叠, 即按从大到小的顺序依次重叠
|
41
|
-
但是 pandas 没有好的处理方法, 为了实现重叠效果, 以下使用设置 ax 处理
|
42
|
-
'''
|
43
|
-
|
44
|
-
try:
|
45
|
-
|
46
|
-
# 创建数据实例
|
47
|
-
_df = pd.DataFrame({item['key']: item['data'] for item in data}, index)
|
48
|
-
|
49
|
-
'''
|
50
|
-
设置图形类型、颜色、宽度
|
51
|
-
注意顺序: 从大到小排列
|
52
|
-
因为图层是叠加的, 所以后面的图层会覆盖前面的图层
|
53
|
-
如果后面图层的较大, 就会前面较小的图层, 所以把图层较大的放在前面, 图层较小的放在后面
|
54
|
-
_, ax = plt.subplots()
|
55
|
-
df.MAX.plot(kind='barh', ax=ax, color='#E74C3C', width=0.8)
|
56
|
-
df.MID.plot(kind='barh', ax=ax, color='#3498DB', width=0.8)
|
57
|
-
df.MIN.plot(kind='barh', ax=ax, color='#2ECC71', width=0.8)
|
58
|
-
'''
|
59
|
-
_, _ax = plt.subplots()
|
60
|
-
for i in data:
|
61
|
-
_df[i['key']].plot(kind=i.get('kind', 'bar'), ax=_ax, color=i.get('color'), width=i.get('width', 0.8))
|
62
|
-
|
63
|
-
'''
|
64
|
-
设置 Label
|
65
|
-
即图片右上角的说明信息, 这里也有顺序, 会按照 handles 中的顺序显示
|
66
|
-
https://stackoverflow.com/a/69897921
|
67
|
-
patch_max = mpatches.Patch(color='#E74C3C', label='Max')
|
68
|
-
patch_mid = mpatches.Patch(color='#3498DB', label='Mid')
|
69
|
-
patch_min = mpatches.Patch(color='#2ECC71', label='Min')
|
70
|
-
plt.legend(handles=[patch_max, patch_mid, patch_min])
|
71
|
-
'''
|
72
|
-
plt.legend(handles=[mpatches.Patch(color=i.get('color'), label=i.get('label')) for i in data])
|
73
|
-
|
74
|
-
# 设置标题
|
75
|
-
plt.title(image.get('title'))
|
76
|
-
|
77
|
-
# 设置上下限
|
78
|
-
plt.xlim(image.get('xlim'))
|
79
|
-
plt.ylim(image.get('ylim'))
|
80
|
-
|
81
|
-
# 创建图片实例
|
82
|
-
_fig = plt.gcf()
|
83
|
-
|
84
|
-
# 设置图片大小
|
85
|
-
# https://www.zhihu.com/question/37221233
|
86
|
-
_fig.set_size_inches(image.get('size', (10, 10)))
|
87
|
-
|
88
|
-
# 保存图片
|
89
|
-
# bbox_inches='tight' 去除图片周边空白
|
90
|
-
_fig.savefig(image.get('path', 'image.png'), dpi=image.get('dpi', 300), bbox_inches='tight')
|
91
|
-
|
92
|
-
# Close a figure window
|
93
|
-
plt.close()
|
94
|
-
|
95
|
-
# Return
|
96
|
-
return True
|
97
|
-
|
98
|
-
except Exception as e:
|
99
|
-
logger.exception(e)
|
100
|
-
return False
|
101
|
-
|
102
|
-
def bar_extend(data=[], index=[], image={}, **kwargs):
|
103
|
-
'''
|
104
|
-
扩展的条形图
|
105
|
-
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.plot.html
|
106
|
-
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.bar.html
|
107
|
-
https://matplotlib.org/stable/gallery/lines_bars_and_markers/barchart.html
|
108
|
-
https://stackoverflow.com/a/68107816
|
109
|
-
'''
|
110
|
-
try:
|
111
|
-
|
112
|
-
_x = np.arange(len(index))
|
113
|
-
|
114
|
-
_bar_width = image.get('width', 0.8)
|
115
|
-
|
116
|
-
_fig, _ax = plt.subplots()
|
117
|
-
|
118
|
-
_rects = _ax.bar(_x, data, _bar_width, label=image.get('label'), color=image.get('color'))
|
119
|
-
|
120
|
-
# Set base
|
121
|
-
_ax.set_title(image.get('title'))
|
122
|
-
_ax.set_xlabel(image.get('xlabel'))
|
123
|
-
_ax.set_ylabel(image.get('ylabel'))
|
124
|
-
_ax.set_xticks(_x, index)
|
125
|
-
|
126
|
-
# Set view limits
|
127
|
-
_ax.set_xlim(image.get('xlim'))
|
128
|
-
_ax.set_ylim(image.get('ylim'))
|
129
|
-
|
130
|
-
# 设置图例
|
131
|
-
_ax.legend()
|
132
|
-
|
133
|
-
# bar 顶部显示 value
|
134
|
-
_ax.bar_label(_rects, padding=3)
|
135
|
-
|
136
|
-
# 自动调整参数, 使图形适合图形区域
|
137
|
-
_fig.tight_layout()
|
138
|
-
|
139
|
-
# 图片大小
|
140
|
-
# 默认 300px 为 1 个单位
|
141
|
-
# 默认 1920px * 1440px, 即 6.4 * 4.8
|
142
|
-
_fig.set_size_inches(image.get('size', (6.4, 4.8)))
|
143
|
-
|
144
|
-
# 生成图片文件
|
145
|
-
# bbox_inches='tight' 去除图片周边空白
|
146
|
-
_fig.savefig(image.get('path', 'image.png'), dpi=image.get('dpi', 300), bbox_inches='tight')
|
147
|
-
|
148
|
-
# Close a figure window
|
149
|
-
plt.close()
|
150
|
-
|
151
|
-
return True
|
152
|
-
|
153
|
-
except Exception as e:
|
154
|
-
logger.exception(e)
|
155
|
-
return False
|