deva 1.3.0__tar.gz → 1.3.2__tar.gz

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.
Files changed (37) hide show
  1. {deva-1.3.0 → deva-1.3.2}/PKG-INFO +1 -1
  2. {deva-1.3.0 → deva-1.3.2}/deva/__init__.py +2 -6
  3. {deva-1.3.0 → deva-1.3.2}/deva/admin.py +374 -122
  4. {deva-1.3.0 → deva-1.3.2}/deva/bus.py +2 -2
  5. {deva-1.3.0 → deva-1.3.2}/deva/core.py +5 -7
  6. deva-1.3.2/deva/gpt.py +290 -0
  7. {deva-1.3.0 → deva-1.3.2}/deva/page.py +0 -2
  8. {deva-1.3.0 → deva-1.3.2}/deva/pipe.py +34 -28
  9. {deva-1.3.0 → deva-1.3.2}/deva/sources.py +472 -195
  10. {deva-1.3.0 → deva-1.3.2}/deva/topic.py +1 -1
  11. {deva-1.3.0 → deva-1.3.2}/deva.egg-info/PKG-INFO +1 -1
  12. {deva-1.3.0 → deva-1.3.2}/deva.egg-info/SOURCES.txt +0 -1
  13. {deva-1.3.0 → deva-1.3.2}/setup.py +1 -1
  14. deva-1.3.0/deva/dask.py +0 -202
  15. deva-1.3.0/deva/gpt.py +0 -174
  16. {deva-1.3.0 → deva-1.3.2}/README.rst +0 -0
  17. {deva-1.3.0 → deva-1.3.2}/deva/browser.py +0 -0
  18. {deva-1.3.0 → deva-1.3.2}/deva/compute.py +0 -0
  19. {deva-1.3.0 → deva-1.3.2}/deva/endpoints.py +0 -0
  20. {deva-1.3.0 → deva-1.3.2}/deva/future.py +0 -0
  21. {deva-1.3.0 → deva-1.3.2}/deva/graph.py +0 -0
  22. {deva-1.3.0 → deva-1.3.2}/deva/lambdas.py +0 -0
  23. {deva-1.3.0 → deva-1.3.2}/deva/monitor.py +0 -0
  24. {deva-1.3.0 → deva-1.3.2}/deva/namespace.py +0 -0
  25. {deva-1.3.0 → deva-1.3.2}/deva/search.py +0 -0
  26. {deva-1.3.0 → deva-1.3.2}/deva/store.py +0 -0
  27. {deva-1.3.0 → deva-1.3.2}/deva/utils/__init__.py +0 -0
  28. {deva-1.3.0 → deva-1.3.2}/deva/utils/simhash.py +0 -0
  29. {deva-1.3.0 → deva-1.3.2}/deva/utils/sqlitedict.py +0 -0
  30. {deva-1.3.0 → deva-1.3.2}/deva/utils/time.py +0 -0
  31. {deva-1.3.0 → deva-1.3.2}/deva/utils/whooshalchemy.py +0 -0
  32. {deva-1.3.0 → deva-1.3.2}/deva/when.py +0 -0
  33. {deva-1.3.0 → deva-1.3.2}/deva.egg-info/dependency_links.txt +0 -0
  34. {deva-1.3.0 → deva-1.3.2}/deva.egg-info/not-zip-safe +0 -0
  35. {deva-1.3.0 → deva-1.3.2}/deva.egg-info/requires.txt +0 -0
  36. {deva-1.3.0 → deva-1.3.2}/deva.egg-info/top_level.txt +0 -0
  37. {deva-1.3.0 → deva-1.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: deva
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: data eval in future
5
5
  Home-page: https://github.com/sostc/deva
6
6
  Author: spark
@@ -18,11 +18,7 @@ from .lambdas import _
18
18
  from .browser import browser,tab,tabs
19
19
  from .gpt import async_gpt,sync_gpt
20
20
 
21
- try:
22
- # import panel as pn
23
- from .dask import DaskStream, scatter
24
21
 
25
- except ImportError:
26
- pass
27
22
 
28
- __version__ = '1.2.4'
23
+
24
+ __version__ = '1.3.2'
@@ -27,43 +27,36 @@ Deva 管理面板 - 基于 PyWebIO 和 Tornado 的 Web 应用程序
27
27
  # coding: utf-8
28
28
 
29
29
  # In[2]:
30
+ import asyncio
30
31
  import os
31
32
  import traceback
33
+ import json
34
+ import time
32
35
  from urllib.parse import urljoin
33
- from deva import (
34
- NW, NB, log, ls, Stream, first, sample, Deva, print, timer,NS,concat, Dtalk
35
- )
36
- from deva.browser import browser,tab,tabs
37
- from deva.page import page #这里为了给流注入 webview 方法和sse 方法
38
- from pywebio.output import (
39
- put_text, put_markdown, set_scope, put_table,use_scope, clear, toast, put_button, put_collapse, put_datatable,
40
- put_buttons, put_row, put_html, put_link, popup,close_popup
41
- )
42
-
43
- from tornado.web import create_signed_value, decode_signed_value
44
36
  from typing import Callable, Union
45
- from pywebio.platform.tornado import webio_handler
46
- from pywebio_battery import put_logbox, logbox_append, set_localstorage, get_localstorage
47
- from pywebio.pin import pin, put_input
48
- from pywebio.session import set_env, run_async, run_js, run_asyncio_coroutine, get_session_implement
49
37
 
50
- from pywebio.input import input, input_group, PASSWORD, textarea, actions, TEXT
51
38
  import pandas as pd
52
- import json
53
- import time
54
-
55
39
  from openai import AsyncOpenAI
56
40
  from tornado.web import create_signed_value, decode_signed_value
57
- from typing import Callable, Union
41
+
42
+ from deva import (
43
+ NW, NB, log, ls, Stream, first, sample, Deva, print, timer, NS, concat, Dtalk
44
+ )
45
+ from deva.browser import browser, tab, tabs
46
+ from deva.bus import warn
47
+ from deva.page import page # 这里为了给流注入 webview 方法和sse 方法
48
+ from deva.gpt import async_gpt, sync_gpt
49
+
50
+ from pywebio.output import (
51
+ put_text, put_markdown, set_scope, put_table, use_scope, clear, toast,
52
+ put_button, put_collapse, put_datatable, put_buttons, put_row, put_html,
53
+ put_link, popup, close_popup, put_tabs
54
+ )
58
55
  from pywebio.platform.tornado import webio_handler
59
56
  from pywebio_battery import put_logbox, logbox_append, set_localstorage, get_localstorage
60
57
  from pywebio.pin import pin, put_input
61
58
  from pywebio.session import set_env, run_async, run_js, run_asyncio_coroutine, get_session_implement
62
59
  from pywebio.input import input, input_group, PASSWORD, textarea, actions, TEXT
63
- import pandas as pd
64
-
65
-
66
- from deva.gpt import async_gpt,sync_gpt
67
60
 
68
61
 
69
62
  @timer(5,start=False)
@@ -89,9 +82,30 @@ async def get_gpt_response(prompt, session=None, scope=None, model_type='deepsee
89
82
  Returns:
90
83
  str: 完整的GPT响应内容
91
84
  """
92
- api_key = NB(model_type)['api_key']
93
- base_url = NB(model_type)['base_url']
94
- model = NB(model_type)['model']
85
+ config = NB(model_type)
86
+ required_configs = ['api_key', 'base_url', 'model']
87
+ missing_configs = [config for config in required_configs if config not in config]
88
+ if missing_configs:
89
+ message = "警告: 在NB配置中缺少以下必要配置项: " + ', '.join(missing_configs) + ". 请确保在其他地方正确设置这些配置项的值。"
90
+ message >> warn
91
+ example_code = "from deva.namespace import NB\n\n"
92
+ example_code += "# 配置示例:\n"
93
+ example_code += "NB('deepseek').update({\n"
94
+ for config in missing_configs:
95
+ if config == 'api_key':
96
+ example_code += " 'api_key': 'your-api-key-here',\n"
97
+ elif config == 'base_url':
98
+ example_code += " 'base_url': 'https://api.example.com/v1',\n"
99
+ elif config == 'model':
100
+ example_code += " 'model': 'model-name',\n"
101
+ example_code += "})"
102
+ example_code >> warn
103
+ return ""
104
+
105
+ api_key = config.get('api_key')
106
+ base_url = config.get('base_url')
107
+ model = config.get('model')
108
+
95
109
  gpt_client = AsyncOpenAI(api_key=api_key, base_url=base_url)
96
110
  start_time = time.time()
97
111
 
@@ -323,7 +337,7 @@ log.start_cache(200, cache_max_age_seconds=60 * 60 * 24 * 30)
323
337
  log.map(lambda x: log.recent(10) >> concat('<br>'))\
324
338
  >> NS('访问日志', cache_max_len=1, cache_max_age_seconds=60 * 60 * 24 * 30)
325
339
 
326
- os.getpid() >> log
340
+
327
341
 
328
342
  streams = [NS('访问日志'), NS('实时新闻'), NS(
329
343
  '涨跌停'), NS('领涨领跌板块'), NS('1分钟板块异动'), NS('30秒板块异动')]
@@ -1023,7 +1037,7 @@ def create_sidebar():
1023
1037
  ''')
1024
1038
  # 在右边栏添加默认iframe
1025
1039
  with use_scope('sidebar'):
1026
- put_html(f'<iframe src="{hash(NS('访问日志'))}" style="width:100%;height:120vh;border:none;"></iframe>')
1040
+ put_html(f"""<iframe src="{hash(NS('访问日志'))}" style="width:100%;height:120vh;border:none;"></iframe>""")
1027
1041
 
1028
1042
  async def init_admin_ui(title):
1029
1043
  """初始化管理界面UI
@@ -1064,38 +1078,6 @@ scheduler.start()
1064
1078
  # 存储任务信息
1065
1079
  tasks = {}
1066
1080
 
1067
- async def async_code_gpt( prompts):
1068
- """
1069
- 异步查询大模型
1070
-
1071
- 参数:
1072
- prompts: 提示词列表或字符串
1073
-
1074
- 返回:
1075
- 大模型返回的结果
1076
- """
1077
- from openai import AsyncOpenAI
1078
- config = NB('deepseek')
1079
- api_key = config['api_key']
1080
- base_url = config['base_url']
1081
- model = config['model'] # 从配置中获取模型名称
1082
-
1083
- # 初始化同步和异步客户端
1084
- async_client = AsyncOpenAI(api_key=api_key, base_url=base_url)
1085
-
1086
- if isinstance(prompts, str):
1087
- prompts = [prompts]
1088
-
1089
- messages = [{"role": "user", "content": prompt} for prompt in prompts]
1090
- completion = await async_client.chat.completions.create(
1091
- model=model,
1092
- messages=messages,
1093
- stream=False,
1094
- max_tokens=8000,
1095
-
1096
- )
1097
-
1098
- return completion.choices[0].message.content
1099
1081
 
1100
1082
  async def watch_topic(topic):
1101
1083
  """分析主题并显示结果"""
@@ -1109,8 +1091,10 @@ async def watch_topic(topic):
1109
1091
  )
1110
1092
  return result
1111
1093
 
1094
+
1112
1095
  async def create_task():
1113
1096
  """创建定时任务"""
1097
+
1114
1098
  task_info = await input_group("创建定时任务", [
1115
1099
  input("任务名称", name="name", type=TEXT),
1116
1100
  textarea("任务描述", name="description", placeholder="请输入任务描述"),
@@ -1131,6 +1115,7 @@ async def create_task():
1131
1115
  return
1132
1116
 
1133
1117
  task_info >>log
1118
+ toast("开始创建定时任务,需要一点时间...") # 通知用户开始创建
1134
1119
  # 生成任务代码
1135
1120
  samplecode = """我有下面这些功能可以调用,使用例子如下,请选择使用里面的功能来合理完成需求:
1136
1121
  from deva import write_to_file,httpx,Dtalk
@@ -1139,12 +1124,12 @@ async def create_task():
1139
1124
  写入文件: 'some text' >>write_to_file('filename')
1140
1125
  抓取网页: response = await httpx(url)
1141
1126
  查找网页标签:response.html.search('<title>{}</title>')
1142
- 发送到钉钉:'@md@焦点分析|'+'some text'>>Dtalk()
1143
- 关注总结查看话题: content = await watch_topic('话题')
1127
+ 发送到钉钉通知我:'@md@焦点分析|'+'some text'>>Dtalk()
1128
+ 定期关注总结查看话题: content = await watch_topic('话题')
1144
1129
  """
1145
1130
  # 调用GPT生成Python代码
1146
- prompt = f"仅限于以下的功能和使用方法:{samplecode},根据以下描述: {description},生成一个Python异步函数,只生成函数主体就可以,不需要执行代码,所有 import 都放在函数内部"
1147
- result = sync_gpt(prompts=prompt)
1131
+ prompt = f"仅限于以下的功能和使用方法:{samplecode},根据以下描述: {description},生成一个Python异步函数,只生成函数主体就可以,不需要执行代码,所有 import 都放在函数内部,详细的代码注释"
1132
+ result = await asyncio.create_task(async_gpt(prompts=prompt))
1148
1133
  def get_python_code_from_deepseek(content):
1149
1134
  # 假设返回的内容中 Python 代码被标记为 ```python ... ```
1150
1135
  import re
@@ -1157,24 +1142,20 @@ async def create_task():
1157
1142
  job_code>>log
1158
1143
  import ast
1159
1144
 
1160
-
1161
-
1162
- # 使用 AST 解析代码
1145
+ # 使用 AST 解析代码
1163
1146
  tree = ast.parse(job_code)
1164
1147
 
1165
- # 获取所有函数定义的名称
1148
+ # 获取所有函数定义的名称
1166
1149
  namespace = {}
1167
1150
  exec(job_code,globals(), namespace) # 动态执行生成的代码
1168
1151
  function_names = [node.name for node in ast.walk(tree) if isinstance(node, ast.AsyncFunctionDef)]
1169
1152
  job_name = function_names[0]
1170
-
1153
+
1171
1154
  job = namespace[job_name]
1172
1155
 
1173
1156
  if task_type == "interval":
1174
1157
  try:
1175
1158
  interval = int(time_value)
1176
- # j = timer(interval=interval,start=True)(job)
1177
- # print(j)
1178
1159
  scheduler.add_job(job, "interval", seconds=interval, id=name)
1179
1160
  except ValueError:
1180
1161
  toast("间隔时间必须为整数!", color="error")
@@ -1187,6 +1168,8 @@ async def create_task():
1187
1168
  toast("时间格式应为 HH:MM!", color="error")
1188
1169
  return
1189
1170
 
1171
+
1172
+
1190
1173
  tasks[name] = {
1191
1174
  "type": task_type,
1192
1175
  "time": time_value,
@@ -1194,70 +1177,169 @@ async def create_task():
1194
1177
  "description": description,
1195
1178
  "job_code": job_code
1196
1179
  }
1197
- toast(f"任务 '{name}' 创建成功!", color="success")
1180
+ # 将任务信息保存到数据库
1181
+ db = NB('tasks')
1182
+ db[name]=tasks[name]
1183
+ toast(f"任务 '{name}' 创建成功!", color="success") # 通知用户创建成功
1184
+ manage_tasks() # 刷新页面对应内容
1198
1185
  # run_js("location.reload()") # 刷新页面,刷新后 session 会失效
1199
-
1200
1186
  def manage_tasks():
1201
1187
  """管理定时任务"""
1202
- if not tasks:
1203
- put_text("当前没有定时任务。")
1204
- return
1205
-
1206
- table_data = []
1207
- for name, info in tasks.items():
1208
- row = [
1209
- name,
1210
- info["description"],
1211
- info["type"],
1212
- info["time"],
1213
- info["status"],
1214
- put_row([
1215
- put_button("停止", onclick=lambda n=name: stop_task(n), color="danger" if info["status"] == "运行中" else "secondary", disabled=info["status"] != "运行中"),
1216
- put_button("启动", onclick=lambda n=name: start_task(n), color="success" if info["status"] == "已停止" else "secondary", disabled=info["status"] != "已停止"),
1217
- put_button("删除", onclick=lambda n=name: delete_task(n), color="warning")
1218
- ])
1219
- ]
1220
- table_data.append(row)
1188
+ async def edit_code(name):
1189
+ code = await textarea("输入代码", code={
1190
+ "mode": "python", # 代码模式,可选 'javascript'、'html' 等
1191
+ "theme": "darcula", # 主题
1192
+ },value=tasks[name]["job_code"])
1193
+ code>>log
1194
+ tasks[name]['job_code']=code
1195
+ NB('tasks')[name] = tasks[name]
1196
+ # 删除老的任务
1197
+ try:
1198
+ scheduler.remove_job(name)
1199
+ except Exception as e:
1200
+ e>>log
1201
+ # 重新加载定时任务
1202
+ job_code = tasks[name]["job_code"]
1203
+ import ast
1204
+ tree = ast.parse(job_code)
1205
+ namespace = {}
1206
+ exec(job_code, globals(), namespace)
1207
+ function_names = [node.name for node in ast.walk(tree) if isinstance(node, ast.AsyncFunctionDef)]
1208
+ job_name = function_names[0]
1209
+ job_func = namespace[job_name]
1210
+ if tasks[name]["type"] == "interval":
1211
+ scheduler.add_job(job_func, "interval", seconds=int(tasks[name]["time"]), id=name)
1212
+ elif tasks[name]["type"] == "cron":
1213
+ hour, minute = map(int, tasks[name]["time"].split(":"))
1214
+ scheduler.add_job(job_func, "cron", hour=hour, minute=minute, id=name)
1215
+
1216
+ print("当前所有计划任务:")
1217
+ for job in scheduler.get_jobs():
1218
+ print(f"任务名称: {job.id}, 任务类型: {job.trigger.__class__.__name__}, 下次执行时间: {job.next_run_time}")
1219
+ # popup('显示代码',content=put_code(content=tasks[name]["job_code"],language='python'))
1220
+ with use_scope('task_management',clear=True):
1221
+ if not tasks:
1222
+ put_text("当前没有定时任务。")
1223
+ return
1224
+ tasks>>log
1225
+ active_table_data = []
1226
+ deleted_table_data = []
1227
+ for name, info in tasks.items():
1228
+ row = [
1229
+ name,
1230
+ info["description"],
1231
+ info["type"],
1232
+ info["time"],
1233
+ info["status"]
1234
+ ]
1235
+ if info["status"] in ["运行中", "已停止"]:
1236
+ row.append(put_row([
1237
+ put_button("源码", onclick=lambda n=name: edit_code(n), color="primary"),
1238
+ put_button("停止", onclick=lambda n=name: stop_task(n), color="danger" if info["status"] == "运行中" else "secondary", disabled=info["status"] != "运行中"),
1239
+ put_button("启动", onclick=lambda n=name: start_task(n), color="success" if info["status"] == "已停止" else "secondary", disabled=info["status"] != "已停止"),
1240
+ put_button("删除", onclick=lambda n=name: delete_task(n), color="warning"),
1241
+ ]))
1242
+ active_table_data.append(row)
1243
+ elif info["status"] == "已删除":
1244
+ row.append(put_row([
1245
+ put_button("源码", onclick=lambda n=name: edit_code(n), color="primary"),
1246
+ put_button("恢复", onclick=lambda n=name: recover_task(n), color="success"),
1247
+ put_button("彻底删除", onclick=lambda n=name: remove_task_forever(n), color="danger"),
1248
+ ]))
1249
+ deleted_table_data.append(row)
1250
+ if active_table_data:
1251
+ put_table(
1252
+ active_table_data,
1253
+ header=["任务名称", "任务描述", "任务类型", "时间/间隔", "状态", "操作"]
1254
+ )
1221
1255
 
1222
- put_table(
1223
- table_data,
1224
- header=["任务名称", "任务描述", "任务类型", "时间/间隔", "状态", "操作"]
1225
- )
1226
-
1256
+ if deleted_table_data:
1257
+ with put_collapse("已删除任务", open=False):
1258
+ put_table(
1259
+ deleted_table_data,
1260
+ header=["任务名称", "任务描述", "任务类型", "时间/间隔", "状态", "操作"]
1261
+ )
1227
1262
 
1228
1263
  def stop_task(name):
1229
1264
  """停止任务"""
1230
- if name in tasks:
1231
- scheduler.pause_job(name)
1232
- tasks[name]["status"] = "已停止"
1233
- toast(f"任务 '{name}' 已停止!", color="success")
1234
- run_js("location.reload()") # 刷新页面
1265
+ scheduler.pause_job(name)
1266
+ tasks[name]["status"] = "已停止"
1267
+ toast(f"任务 '{name}' 已停止!", color="success")
1268
+ run_js("location.reload()") # 刷新页面
1269
+ # 更新数据库中的任务状态
1270
+ db = NB('tasks')
1271
+ db[name]=tasks[name]
1272
+ manage_tasks()
1235
1273
 
1236
1274
  def start_task(name):
1237
1275
  """启动任务"""
1238
- if name in tasks:
1239
- scheduler.resume_job(name)
1240
- tasks[name]["status"] = "运行中"
1241
- toast(f"任务 '{name}' 已启动!", color="success")
1242
- run_js("location.reload()") # 刷新页面
1276
+ scheduler.resume_job(name)
1277
+ tasks[name]["status"] = "运行中"
1278
+ toast(f"任务 '{name}' 已启动!", color="success")
1279
+ run_js("location.reload()") # 刷新页面
1280
+ # 更新数据库中的任务状态
1281
+ db = NB('tasks')
1282
+ db[name]=tasks[name]
1283
+ manage_tasks()
1243
1284
 
1244
1285
  def delete_task(name):
1245
1286
  """删除任务"""
1246
- if name in tasks:
1247
- scheduler.remove_job(name)
1248
- del tasks[name]
1249
- toast(f"任务 '{name}' 已删除!", color="success")
1250
- run_js("location.reload()") # 刷新页面
1287
+ try:
1288
+ scheduler.remove_job(name)>>log
1289
+ except Exception as e:
1290
+ e>>log
1291
+ tasks[name].update({"status": "已删除"})
1292
+ # 更新数据库中的任务状态为已删除
1293
+ db = NB('tasks')
1294
+ db[name]=tasks[name]
1295
+ toast(f"任务 '{name}' 已删除!", color="success")
1296
+ manage_tasks()
1297
+
1298
+ def recover_task(name):
1299
+ db = NB('tasks')
1300
+ info = db[name]
1301
+ info['status'] = '运行中'
1302
+ tasks[name] = info
1303
+ db[name] = tasks[name]
1304
+
1305
+ job_code = info["job_code"]
1306
+ # 对job_code进行转换,生成函数
1307
+ import ast
1308
+ tree = ast.parse(job_code)
1309
+ namespace = {}
1310
+ exec(job_code, globals(), namespace)
1311
+ function_names = [node.name for node in ast.walk(tree) if isinstance(node, ast.AsyncFunctionDef)]
1312
+ job_name = function_names[0]
1251
1313
 
1314
+ job_func = namespace[job_name]
1315
+ if info["type"] == "interval":
1316
+ scheduler.add_job(job_func, "interval", seconds=int(info["time"]), id=name)
1317
+ elif info["type"] == "cron":
1318
+ hour, minute = map(int, info["time"].split(":"))
1319
+ scheduler.add_job(job_func, "cron", hour=hour, minute=minute, id=name)
1320
+
1321
+
1322
+ manage_tasks()
1323
+
1324
+ def remove_task_forever(name):
1325
+ """彻底删除任务,永久无法恢复"""
1326
+ try:
1327
+ scheduler.remove_job(name)>>log
1328
+ except Exception as e:
1329
+ e>>log
1330
+ del tasks[name]
1331
+ # 更新数据库中的任务状态为已删除
1332
+ db = NB('tasks')
1333
+ del db[name]
1334
+ toast(f"任务 '{name}' 已删除!", color="success")
1335
+ manage_tasks()
1336
+
1252
1337
  async def taskadmin():
1253
1338
  await init_admin_ui('Deva任务管理')
1254
1339
 
1255
-
1256
1340
  put_button("创建定时任务", onclick=create_task)
1257
1341
  manage_tasks() # 直接展示任务列表
1258
1342
  set_scope('task_log')
1259
-
1260
-
1261
1343
 
1262
1344
 
1263
1345
  async def dbadmin():
@@ -1270,7 +1352,151 @@ async def streamadmin():
1270
1352
  put_markdown('### 数据流')
1271
1353
  put_buttons([s.name for s in streams],onclick=stream_click)
1272
1354
 
1273
-
1355
+ async def inspect_object(obj):
1356
+ """在Web界面上展示Python对象的所有相关信息
1357
+
1358
+ 参数:
1359
+ obj: 要展示的Python对象
1360
+ """
1361
+ # 创建popup内容
1362
+ content = []
1363
+
1364
+ # 显示对象基本信息
1365
+ content.append(put_markdown(f"### 对象信息"))
1366
+ content.append(put_table([
1367
+ ['类型', type(obj).__name__],
1368
+ ['ID', id(obj)],
1369
+ ['哈希值', hash(obj) if not isinstance(obj, dict) else 'N/A (字典不可哈希)'],
1370
+ ['可调用', callable(obj)]
1371
+ ]))
1372
+
1373
+ # 显示对象文档
1374
+ if obj.__doc__:
1375
+ content.append(put_markdown("#### 文档说明"))
1376
+ if obj.__doc__:
1377
+ # 将文档内容按行分割
1378
+ doc_lines = obj.__doc__.split('\n')
1379
+ formatted_doc = []
1380
+ in_code_block = False
1381
+
1382
+ for line in doc_lines:
1383
+ # 检测代码块开始
1384
+ if line.strip().startswith('>>>') or line.strip().startswith('...'):
1385
+ if not in_code_block:
1386
+ formatted_doc.append('```python')
1387
+ in_code_block = True
1388
+ formatted_doc.append(line)
1389
+ else:
1390
+ if in_code_block:
1391
+ formatted_doc.append('```')
1392
+ in_code_block = False
1393
+ formatted_doc.append(line)
1394
+
1395
+ # 处理最后一个代码块
1396
+ if in_code_block:
1397
+ formatted_doc.append('```')
1398
+
1399
+ # 将格式化后的文档内容添加到显示
1400
+ content.append(put_markdown('\n'.join(formatted_doc)))
1401
+
1402
+ # 显示对象属性
1403
+ content.append(put_markdown("#### 属性"))
1404
+ attrs = []
1405
+ for attr in dir(obj):
1406
+ if not attr.startswith('__'):
1407
+ try:
1408
+ value = getattr(obj, attr)
1409
+ # 过滤掉方法,只显示非可调用属性
1410
+ if not callable(value):
1411
+ attrs.append([attr, type(value).__name__, str(value)[:100]])
1412
+ except Exception as e:
1413
+ attrs.append([attr, '无法访问', str(e)])
1414
+ content.append(put_table([['属性名', '类型', '值']] + attrs))
1415
+
1416
+ # 显示对象方法
1417
+ content.append(put_markdown("#### 方法"))
1418
+ methods = []
1419
+ for attr in dir(obj):
1420
+ if not attr.startswith('__'):
1421
+ try:
1422
+ value = getattr(obj, attr)
1423
+ if callable(value):
1424
+ doc = value.__doc__ or '无文档说明'
1425
+ methods.append([attr, doc[:200]])
1426
+ except Exception as e:
1427
+ methods.append([attr, f'无法访问: {str(e)}'])
1428
+ content.append(put_table([['方法名', '文档说明']] + methods))
1429
+
1430
+ # 在popup中显示内容
1431
+ popup(title="对象详情", content=content, size='large')
1432
+ async def document():
1433
+ await init_admin_ui("Deva管理面板")
1434
+ # 获取deva下所有子模块
1435
+ import deva
1436
+ import pkgutil
1437
+ import inspect
1438
+
1439
+ # 遍历deva下的所有子模块
1440
+ # 创建tab容器
1441
+ tabs = []
1442
+
1443
+ for module_info in pkgutil.iter_modules(deva.__path__):
1444
+ module_name = module_info.name
1445
+ try:
1446
+ # 动态导入模块
1447
+ module = __import__(f'deva.{module_name}', fromlist=['*'])
1448
+
1449
+ # 创建模块内容
1450
+ with use_scope(f'module_{module_name}', clear=True):
1451
+ # 创建类和函数表格
1452
+ module_table = [['名称', '类型', '文档说明']]
1453
+
1454
+ # 获取模块中定义的成员,包括全局变量
1455
+ members = inspect.getmembers(module)
1456
+
1457
+ # 获取模块的全局变量
1458
+ global_vars = {k: v for k, v in module.__dict__.items()
1459
+ if not k.startswith('__') and not inspect.ismodule(v)}
1460
+
1461
+ # 合并成员和全局变量
1462
+ all_objects = {**dict(members), **global_vars}
1463
+
1464
+ # 过滤出类和函数和全局对象
1465
+ for name, obj in all_objects.items():
1466
+ # 检查是否为类、函数、被装饰的函数或全局对象
1467
+ if (inspect.isclass(obj) or inspect.isfunction(obj) or
1468
+ hasattr(obj, '__wrapped__') or
1469
+ (not inspect.ismodule(obj) and not name.startswith('_'))) :
1470
+ doc = obj.__doc__ or '无文档说明'
1471
+ if isinstance(doc, str):
1472
+ # 判断对象类型
1473
+ if inspect.isclass(obj):
1474
+ obj_type = '类'
1475
+ elif inspect.isfunction(obj) or hasattr(obj, '__wrapped__'):
1476
+ obj_type = '函数'
1477
+ else:
1478
+ obj_type = '全局对象'
1479
+
1480
+ # 对于被装饰器修饰的函数,获取原始函数的文档
1481
+ if hasattr(obj, '__wrapped__'):
1482
+ doc = obj.__wrapped__.__doc__ or doc
1483
+
1484
+ # 添加点击事件
1485
+ action_button = put_button(name, onclick=lambda o=obj: run_async(inspect_object(o)))
1486
+ module_table.append([action_button, obj_type, doc[:200]])
1487
+ # 添加模块内容到tab
1488
+ tabs.append({
1489
+ 'title': module_name,
1490
+ 'content': put_table(module_table)
1491
+ })
1492
+ except ImportError as e:
1493
+ print(f"无法导入模块 {module_name}: {e}")
1494
+ tabs.append({
1495
+ 'title': module_name,
1496
+ 'content': put_text(f"无法加载模块: {str(e)}")
1497
+ })
1498
+ # 显示所有模块的tab
1499
+ put_tabs(tabs)
1274
1500
  async def main():
1275
1501
  # await my_timer()
1276
1502
  # 这个将会把会话协程卡在这里不动,采用 run_async则不会堵塞
@@ -1284,6 +1510,10 @@ async def main():
1284
1510
 
1285
1511
  set_table_style() # 调用函数应用样式
1286
1512
 
1513
+ # 获取所有函数定义的名称
1514
+
1515
+
1516
+
1287
1517
 
1288
1518
  # 获取所有主题数据
1289
1519
  topics = NB('topics').items()
@@ -1892,7 +2122,7 @@ def create_nav_menu():
1892
2122
  {name: '数据库', path: '/dbadmin', action: () => window.location.href = '/dbadmin'},
1893
2123
  {name: '实时流', path: '/streamadmin', action: () => window.location.href = '/streamadmin'},
1894
2124
  {name: '任务', path: '/taskadmin', action: () => window.location.href = '/taskadmin'},
1895
- {name: '关于', path: '#', action: () => alert('Deva管理面板 v1.0')}
2125
+ {name: '文档', path: '/document', action: () => window.location.href = '/document'}
1896
2126
  ];
1897
2127
 
1898
2128
  menuItems.forEach(item => {
@@ -1935,15 +2165,37 @@ def create_nav_menu():
1935
2165
  if __name__ == '__main__':
1936
2166
  from deva.page import page
1937
2167
 
2168
+ # 从数据库读取任务信息
2169
+ db = NB('tasks')
2170
+ for name, info in db.items():
2171
+ tasks[name] = info
2172
+ if info["status"] == "运行中":
2173
+ job_code = info["job_code"]
2174
+ # 对job_code进行转换,生成函数
2175
+ import ast
2176
+ tree = ast.parse(job_code)
2177
+ namespace = {}
2178
+ exec(job_code, globals(), namespace)
2179
+ function_names = [node.name for node in ast.walk(tree) if isinstance(node, ast.AsyncFunctionDef)]
2180
+ job_name = function_names[0]
2181
+
2182
+ job_func = namespace[job_name]
2183
+ if info["type"] == "interval":
2184
+ scheduler.add_job(job_func, "interval", seconds=int(info["time"]), id=name)
2185
+ elif info["type"] == "cron":
2186
+ hour, minute = map(int, info["time"].split(":"))
2187
+ scheduler.add_job(job_func, "cron", hour=hour, minute=minute, id=name)
1938
2188
 
1939
2189
  # 创建一个名为'stream_webview'的Web服务器实例,监听所有网络接口(0.0.0.0)
1940
2190
  # 然后为该服务器添加路由处理器,将'/admin'路径映射到dbadmin处理函数
1941
2191
  # 使用PyWebIO的webio_handler进行封装,并指定CDN地址
2192
+ cdn='https://fastly.jsdelivr.net/gh/wang0618/PyWebIO-assets@v1.8.3/'
1942
2193
  handlers = [
1943
- (r'/dbadmin', webio_handler(dbadmin, cdn='https://fastly.jsdelivr.net/gh/wang0618/PyWebIO-assets@v1.8.3/')),
1944
- (r'/streamadmin', webio_handler(streamadmin, cdn='https://fastly.jsdelivr.net/gh/wang0618/PyWebIO-assets@v1.8.3/')),
1945
- (r'/', webio_handler(main, cdn='https://fastly.jsdelivr.net/gh/wang0618/PyWebIO-assets@v1.8.3/')),
1946
- (r'/taskadmin', webio_handler(taskadmin, cdn='https://fastly.jsdelivr.net/gh/wang0618/PyWebIO-assets@v1.8.3/'))
2194
+ (r'/dbadmin', webio_handler(dbadmin, cdn=cdn)),
2195
+ (r'/streamadmin', webio_handler(streamadmin, cdn=cdn)),
2196
+ (r'/', webio_handler(main, cdn=cdn)),
2197
+ (r'/taskadmin', webio_handler(taskadmin, cdn=cdn)),
2198
+ (r'/document', webio_handler(document, cdn=cdn))
1947
2199
  ]
1948
2200
  NW('stream_webview',host='0.0.0.0').application.add_handlers('.*$', handlers)
1949
2201