funboost 48.8__py3-none-any.whl → 49.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.

Potentially problematic release.


This version of funboost might be problematic. Click here for more details.

Files changed (33) hide show
  1. funboost/__init__.py +1 -1
  2. funboost/concurrent_pool/custom_threadpool_executor.py +1 -1
  3. funboost/constant.py +16 -2
  4. funboost/consumers/base_consumer.py +42 -29
  5. funboost/consumers/rabbitmq_amqpstorm_consumer.py +5 -0
  6. funboost/consumers/redis_filter.py +47 -31
  7. funboost/core/active_cousumer_info_getter.py +47 -7
  8. funboost/core/booster.py +1 -0
  9. funboost/core/current_task.py +17 -0
  10. funboost/core/func_params_model.py +30 -17
  11. funboost/core/loggers.py +1 -0
  12. funboost/funboost_config_deafult.py +1 -1
  13. funboost/function_result_web/__pycache__/app.cpython-37.pyc +0 -0
  14. funboost/function_result_web/__pycache__/functions.cpython-37.pyc +0 -0
  15. funboost/function_result_web/__pycache__/functions.cpython-39.pyc +0 -0
  16. funboost/function_result_web/app_debug_start.py +1 -1
  17. funboost/function_result_web/functions.py +10 -1
  18. funboost/function_result_web/static/js/form-memory.js +92 -0
  19. funboost/function_result_web/static/js_cdn/chart.js +20 -0
  20. funboost/function_result_web/templates/index.html +31 -1
  21. funboost/function_result_web/templates/queue_op.html +418 -27
  22. funboost/function_result_web/templates/rpc_call.html +51 -37
  23. funboost/publishers/rabbitmq_amqpstorm_publisher.py +1 -1
  24. funboost/publishers/redis_publisher_priority.py +2 -2
  25. funboost/utils/dependency_packages_in_pythonpath/aioredis/readme.md +1 -1
  26. funboost/utils/dependency_packages_in_pythonpath/readme.md +1 -1
  27. {funboost-48.8.dist-info → funboost-49.0.dist-info}/METADATA +176 -82
  28. {funboost-48.8.dist-info → funboost-49.0.dist-info}/RECORD +32 -31
  29. {funboost-48.8.dist-info → funboost-49.0.dist-info}/WHEEL +1 -1
  30. funboost/function_result_web/templates/index_/321/204/342/225/225/320/235/321/205/320/237/320/277/321/206/320/232/320/250/321/205/320/237/320/260.html +0 -153
  31. {funboost-48.8.dist-info → funboost-49.0.dist-info}/LICENSE +0 -0
  32. {funboost-48.8.dist-info → funboost-49.0.dist-info}/entry_points.txt +0 -0
  33. {funboost-48.8.dist-info → funboost-49.0.dist-info}/top_level.txt +0 -0
@@ -127,17 +127,18 @@ class BoosterParams(BaseJsonAbleModel):
127
127
  """
128
128
 
129
129
  queue_name: str # 队列名字,必传项,每个函数要使用不同的队列名字.
130
+ broker_kind: str = BrokerEnum.SQLITE_QUEUE # 中间件选型见3.1章节 https://funboost.readthedocs.io/zh-cn/latest/articles/c3.html
130
131
 
131
132
  """如果设置了qps,并且cocurrent_num是默认的50,会自动开了500并发,由于是采用的智能线程池任务少时候不会真开那么多线程而且会自动缩小线程数量。具体看ThreadPoolExecutorShrinkAble的说明
132
133
  由于有很好用的qps控制运行频率和智能扩大缩小的线程池,此框架建议不需要理会和设置并发数量只需要关心qps就行了,框架的并发是自适应并发数量,这一点很强很好用。"""
133
134
  concurrent_mode: str = ConcurrentModeEnum.THREADING # 并发模式,支持THREADING,GEVENT,EVENTLET,ASYNC,SINGLE_THREAD并发,multi_process_consume 支持协程/线程 叠加多进程并发,性能炸裂.
134
135
  concurrent_num: int = 50 # 并发数量,并发种类由concurrent_mode决定
135
136
  specify_concurrent_pool: typing.Optional[FunboostBaseConcurrentPool] = None # 使用指定的线程池/携程池,可以多个消费者共使用一个线程池,节约线程.不为None时候。threads_num失效
136
- specify_async_loop: asyncio.AbstractEventLoop = None # 指定的async的loop循环,设置并发模式为async才能起作用。 有些包例如aiohttp,请求和httpclient的实例化不能处在两个不同的loop中,可以传过来.
137
+ specify_async_loop: typing.Optional[asyncio.AbstractEventLoop] = None # 指定的async的loop循环,设置并发模式为async才能起作用。 有些包例如aiohttp,发送请求和httpclient的实例化不能处在两个不同的loop中,可以传过来.
137
138
 
138
139
  """qps:
139
140
  强悍的控制功能,指定1秒内的函数执行次数,例如可以是小数0.01代表每100秒执行一次,也可以是50代表1秒执行50次.为None则不控频。 设置qps时候,不需要指定并发数量,funboost的能够自适应智能动态调节并发池大小."""
140
- qps: typing.Union[float, int] = None
141
+ qps: typing.Union[float, int, None] = None
141
142
  """is_using_distributed_frequency_control:
142
143
  是否使用分布式空频(依赖redis统计消费者数量,然后频率平分),默认只对当前实例化的消费者空频有效。假如实例化了2个qps为10的使用同一队列名的消费者,并且都启动,则每秒运行次数会达到20。
143
144
  如果使用分布式空频则所有消费者加起来的总运行次数是10。"""
@@ -155,7 +156,7 @@ class BoosterParams(BaseJsonAbleModel):
155
156
  is_push_to_dlx_queue_when_retry_max_times: bool = False # 函数达到最大重试次数仍然没成功,是否发送到死信队列,死信队列的名字是 队列名字 + _dlx。
156
157
 
157
158
 
158
- consumin_function_decorator: typing.Callable = None # 函数的装饰器。因为此框架做参数自动转指点,需要获取精准的入参名称,不支持在消费函数上叠加 @ *args **kwargs的装饰器,如果想用装饰器可以这里指定。
159
+ consumin_function_decorator: typing.Optional[typing.Callable] = None # 函数的装饰器。因为此框架做参数自动转指点,需要获取精准的入参名称,不支持在消费函数上叠加 @ *args **kwargs的装饰器,如果想用装饰器可以这里指定。
159
160
  function_timeout: typing.Union[int, float,None] = None # 超时秒数,函数运行超过这个时间,则自动杀死函数。为0是不限制。 谨慎使用,非必要别去设置超时时间,设置后性能会降低(因为需要把用户函数包装到另一个线单独的程中去运行),而且突然强制超时杀死运行中函数,可能会造成死锁.(例如用户函数在获得线程锁后突然杀死函数,别的线程再也无法获得锁了)
160
161
 
161
162
  log_level: int = logging.DEBUG # 消费者和发布者的日志级别,建议设置DEBUG级别,不然无法知道正在运行什么消息
@@ -166,7 +167,7 @@ class BoosterParams(BaseJsonAbleModel):
166
167
  is_show_message_get_from_broker: bool = False # 运行时候,是否记录从消息队列获取出来的消息内容
167
168
  is_print_detail_exception: bool = True # 消费函数出错时候,是否打印详细的报错堆栈,为False则只打印简略的报错信息不包含堆栈.
168
169
 
169
- msg_expire_senconds: typing.Union[float, int] = None # 消息过期时间,可以设置消息是多久之前发布的就丢弃这条消息,不运行. 为None则永不丢弃
170
+ msg_expire_senconds: typing.Union[float, int,None] = None # 消息过期时间,可以设置消息是多久之前发布的就丢弃这条消息,不运行. 为None则永不丢弃
170
171
 
171
172
  do_task_filtering: bool = False # 是否对函数入参进行过滤去重.
172
173
  task_filtering_expire_seconds: int = 0 # 任务过滤的失效期,为0则永久性过滤任务。例如设置过滤过期时间是1800秒 , 30分钟前发布过1 + 2 的任务,现在仍然执行,如果是30分钟以内执行过这个任务,则不执行1 + 2
@@ -174,7 +175,7 @@ class BoosterParams(BaseJsonAbleModel):
174
175
  function_result_status_persistance_conf: FunctionResultStatusPersistanceConfig = FunctionResultStatusPersistanceConfig(
175
176
  is_save_result=False, is_save_status=False, expire_seconds=7 * 24 * 3600, is_use_bulk_insert=False) # 是否保存函数的入参,运行结果和运行状态到mongodb。这一步用于后续的参数追溯,任务统计和web展示,需要安装mongo。
176
177
 
177
- user_custom_record_process_info_func: typing.Callable = None # 提供一个用户自定义的保存消息处理记录到某个地方例如mysql数据库的函数,函数仅仅接受一个入参,入参类型是 FunctionResultStatus,用户可以打印参数
178
+ user_custom_record_process_info_func: typing.Optional[typing.Callable] = None # 提供一个用户自定义的保存消息处理记录到某个地方例如mysql数据库的函数,函数仅仅接受一个入参,入参类型是 FunctionResultStatus,用户可以打印参数
178
179
 
179
180
  is_using_rpc_mode: bool = False # 是否使用rpc模式,可以在发布端获取消费端的结果回调,但消耗一定性能,使用async_result.result时候会等待阻塞住当前线程。
180
181
  rpc_result_expire_seconds: int = 600 # 保存rpc结果的过期时间.
@@ -190,10 +191,11 @@ class BoosterParams(BaseJsonAbleModel):
190
191
 
191
192
  is_auto_start_consuming_message: bool = False # 是否在定义后就自动启动消费,无需用户手动写 .consume() 来启动消息消费。
192
193
 
193
- consuming_function: typing.Callable = None # 消费函数,在@boost时候不用指定,因为装饰器知道下面的函数.
194
- consuming_function_raw: typing.Callable = None
194
+ consuming_function: typing.Optional[typing.Callable] = None # 消费函数,在@boost时候不用指定,因为装饰器知道下面的函数.
195
+ consuming_function_raw: typing.Optional[typing.Callable] = None # 不需要传递,自动生成
196
+ consuming_function_name: str = '' # 不需要传递,自动生成
195
197
 
196
- broker_kind: str = BrokerEnum.SQLITE_QUEUE # 中间件选型见3.1章节 https://funboost.readthedocs.io/zh-cn/latest/articles/c3.html
198
+
197
199
 
198
200
  broker_exclusive_config: dict = {} # 加上一个不同种类中间件非通用的配置,不同中间件自身独有的配置,不是所有中间件都兼容的配置,因为框架支持30种消息队列,消息队列不仅仅是一般的先进先出queue这么简单的概念,
199
201
  # 例如kafka支持消费者组,rabbitmq也支持各种独特概念例如各种ack机制 复杂路由机制,有的中间件原生能支持消息优先级有的中间件不支持,每一种消息队列都有独特的配置参数意义,可以通过这里传递。每种中间件能传递的键值对可以看consumer类的 BROKER_EXCLUSIVE_CONFIG_DEFAULT
@@ -221,6 +223,7 @@ class BoosterParams(BaseJsonAbleModel):
221
223
 
222
224
  @root_validator(skip_on_failure=True)
223
225
  def check_values(cls, values: dict):
226
+
224
227
 
225
228
  # 如果设置了qps,并且cocurrent_num是默认的50,会自动开了500并发,由于是采用的智能线程池任务少时候不会真开那么多线程而且会自动缩小线程数量。具体看ThreadPoolExecutorShrinkAble的说明
226
229
  # 由于有很好用的qps控制运行频率和智能扩大缩小的线程池,此框架建议不需要理会和设置并发数量只需要关心qps就行了,框架的并发是自适应并发数量,这一点很强很好用。
@@ -233,7 +236,8 @@ class BoosterParams(BaseJsonAbleModel):
233
236
 
234
237
  if values['concurrent_mode'] not in ConcurrentModeEnum.__dict__.values():
235
238
  raise ValueError('设置的并发模式不正确')
236
- if values['broker_kind'] in [BrokerEnum.REDIS_ACK_ABLE, BrokerEnum.REDIS_STREAM, BrokerEnum.REDIS_PRIORITY, BrokerEnum.RedisBrpopLpush]:
239
+ if values['broker_kind'] in [BrokerEnum.REDIS_ACK_ABLE, BrokerEnum.REDIS_STREAM, BrokerEnum.REDIS_PRIORITY,
240
+ BrokerEnum.RedisBrpopLpush,BrokerEnum.REDIS,BrokerEnum.REDIS_PUBSUB]:
237
241
  values['is_send_consumer_hearbeat_to_redis'] = True # 需要心跳进程来辅助判断消息是否属于掉线或关闭的进程,需要重回队列
238
242
  # if not set(values.keys()).issubset(set(BoosterParams.__fields__.keys())):
239
243
  # raise ValueError(f'{cls.__name__} 的字段包含了父类 BoosterParams 不存在的字段')
@@ -291,19 +295,28 @@ class PriorityConsumingControlConfig(BaseJsonAbleModel):
291
295
 
292
296
  function_timeout: typing.Union[float, int,None] = None
293
297
 
294
- max_retry_times: int = None
298
+ max_retry_times: typing.Union[int,None] = None
295
299
 
296
- is_print_detail_exception: bool = None
300
+ is_print_detail_exception: typing.Union[bool,None] = None
297
301
 
298
- msg_expire_senconds: int = None
302
+ msg_expire_senconds: typing.Union[float, int,None] = None
299
303
 
300
- is_using_rpc_mode: bool = None
304
+ is_using_rpc_mode: typing.Union[bool,None] = None
301
305
 
302
- countdown: typing.Union[float, int] = None
303
- eta: typing.Union[datetime.datetime, str] = None # 时间对象, 或 %Y-%m-%d %H:%M:%S 字符串。
306
+ countdown: typing.Union[float, int,None] = None
307
+ eta: typing.Union[datetime.datetime, str,None] = None # 时间对象, 或 %Y-%m-%d %H:%M:%S 字符串。
304
308
  misfire_grace_time: typing.Union[int, None] = None
305
309
 
306
310
  other_extra_params: dict = None # 其他参数, 例如消息优先级 , priority_control_config=PriorityConsumingControlConfig(other_extra_params={'priroty': priorityxx}),
311
+
312
+ """filter_str:
313
+ 用户指定过滤字符串, 例如函数入参是 def fun(userid,username,sex,user_description),
314
+ 默认是所有入参一起组成json来过滤,但其实只把userid的值来过滤就好了。所以如果需要精准的按照什么过滤,用户来灵活指定一个字符串就好了
315
+
316
+ 用法见文档4.35
317
+ f3.publish(msg={'a':i,'b':i*2},priority_control_config=PriorityConsumingControlConfig(filter_str=str(i)))
318
+ """
319
+ filter_str :typing.Optional[str] = None
307
320
 
308
321
  @root_validator(skip_on_failure=True)
309
322
  def cehck_values(cls, values: dict):
@@ -324,8 +337,8 @@ class PublisherParams(BaseJsonAbleModel):
324
337
  logger_name: str = '' # 队列消费者发布者的日志命名空间.
325
338
  log_filename: typing.Optional[str] = None
326
339
  clear_queue_within_init: bool = False # with 语法发布时候,先清空消息队列
327
- consuming_function: typing.Callable = None # consuming_function 作用是 inspect 模块获取函数的入参信息
328
- broker_kind: str = None
340
+ consuming_function: typing.Optional[typing.Callable] = None # consuming_function 作用是 inspect 模块获取函数的入参信息
341
+ broker_kind: typing.Optional[str] = None
329
342
  broker_exclusive_config: dict = {}
330
343
  should_check_publish_func_params: bool = True # 消息发布时候是否校验消息发布内容,比如有的人发布消息,函数只接受a,b两个入参,他去传2个入参,或者传参不存在的参数名字, 如果消费函数你非要写*args,**kwargs,那就需要关掉发布消息时候的函数入参检查
331
344
  publisher_override_cls: typing.Optional[typing.Type] = None
funboost/core/loggers.py CHANGED
@@ -53,3 +53,4 @@ if __name__ == '__main__':
53
53
  logger1 = get_funboost_file_logger('name1')
54
54
  logger1.info('啦啦啦啦啦啦啦')
55
55
  logger1.error('错错错')
56
+
@@ -42,7 +42,7 @@ class BrokerConnConfig(DataClassBase):
42
42
  REDIS_USERNAME = ''
43
43
  REDIS_PASSWORD = ''
44
44
  REDIS_PORT = 6379
45
- REDIS_DB = 7 # redis消息队列所在db,请不要在这个db放太多其他键值对,框架里面有的功能会scan扫描unacked的键名,使用单独的db。
45
+ REDIS_DB = 7 # redis消息队列所在db,请不要在这个db放太多其他键值对,以及方便你自己可视化查看你的redis db,框架里面有的功能会scan扫描unacked的键名,使用单独的db。
46
46
  REDIS_DB_FILTER_AND_RPC_RESULT = 8 # 如果函数做任务参数过滤 或者使用rpc获取结果,使用这个db,因为这个db的键值对多,和redis消息队列db分开
47
47
  REDIS_URL = f'redis://{REDIS_USERNAME}:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'
48
48
 
@@ -2,5 +2,5 @@ from funboost.function_result_web.app import app
2
2
 
3
3
 
4
4
  if __name__ == '__main__':
5
- app.run(debug=True, threaded=True, host='0.0.0.0', port=27018)
5
+ app.run(debug=True, threaded=True, host='0.0.0.0', port=27019)
6
6
 
@@ -167,7 +167,7 @@ def rpc_call(queue_name, msg_body, need_result, timeout):
167
167
  # if booster.boost_params.is_using_rpc_mode is False:
168
168
  # raise ValueError(f' need_result 为true,{booster.queue_name} 队列消费者 需要@boost设置支持rpc模式')
169
169
 
170
- async_result = publisher.publish(msg_body,priority_control_config=PriorityConsumingControlConfig(is_using_rpc_mode=True))
170
+ async_result: AsyncResult = publisher.publish(msg_body,priority_control_config=PriorityConsumingControlConfig(is_using_rpc_mode=True))
171
171
  async_result.set_timeout(timeout)
172
172
  status_and_result = async_result.status_and_result
173
173
  # print(status_and_result)
@@ -175,6 +175,9 @@ def rpc_call(queue_name, msg_body, need_result, timeout):
175
175
  else:
176
176
  async_result =publisher.publish(msg_body)
177
177
  task_id = async_result.task_id
178
+ if status_and_result['success'] is False:
179
+ return dict(succ=False, msg=f'{queue_name} 队列,消息发布成功,但是函数执行失败',
180
+ status_and_result=status_and_result,task_id=task_id)
178
181
  return dict(succ=True, msg=f'{queue_name} 队列,消息发布成功',
179
182
  status_and_result=status_and_result,task_id=task_id)
180
183
  except Exception as e:
@@ -186,6 +189,12 @@ def get_result_by_task_id(task_id,timeout):
186
189
  async_result = AsyncResult(task_id)
187
190
  async_result.set_timeout(timeout)
188
191
  status_and_result = async_result.status_and_result
192
+ if status_and_result is None:
193
+ return dict(succ=False, msg=f'{task_id} 不存在 或 超时 或 结果已过期',
194
+ status_and_result=status_and_result,task_id=task_id)
195
+ if status_and_result['success'] is False:
196
+ return dict(succ=False, msg=f'{task_id} 执行失败',
197
+ status_and_result=status_and_result,task_id=task_id)
189
198
  return dict(succ=True, msg=f'task_id:{task_id} 获取结果成功',
190
199
  status_and_result=status_and_result,task_id=task_id)
191
200
 
@@ -0,0 +1,92 @@
1
+ // 表单记忆功能 - 保存和恢复iframe内的表单值
2
+ (function() {
3
+ // 简化版 - 只保存基本输入值
4
+ function saveFormInputs() {
5
+ const iframe = document.getElementById('content');
6
+ if (!iframe || !iframe.contentWindow) return;
7
+
8
+ const pageId = iframe.src.split('/').pop().replace('.html', '');
9
+
10
+ try {
11
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
12
+ const formData = {};
13
+
14
+ // 保存所有输入元素值
15
+ const inputs = iframeDoc.querySelectorAll('input, textarea, select');
16
+ inputs.forEach(input => {
17
+ const inputId = input.id || input.name || input.getAttribute('placeholder');
18
+ if (inputId) {
19
+ formData[inputId] = input.value;
20
+ }
21
+ });
22
+
23
+ // 特殊处理下拉框的选中文本
24
+ const selects = iframeDoc.querySelectorAll('select');
25
+ selects.forEach(select => {
26
+ const inputId = select.id || select.name;
27
+ if (inputId && select.selectedIndex >= 0 && select.options[select.selectedIndex]) {
28
+ formData[`${inputId}_text`] = select.options[select.selectedIndex].text;
29
+ }
30
+ });
31
+
32
+ localStorage.setItem(`funboost_form_${pageId}`, JSON.stringify(formData));
33
+ } catch (e) {
34
+ console.error("保存表单数据失败:", e);
35
+ }
36
+ }
37
+
38
+ // 简化版 - 只恢复基本输入值
39
+ function restoreFormInputs() {
40
+ setTimeout(() => {
41
+ const iframe = document.getElementById('content');
42
+ if (!iframe || !iframe.contentWindow) return;
43
+
44
+ const pageId = iframe.src.split('/').pop().replace('.html', '');
45
+ const savedData = localStorage.getItem(`funboost_form_${pageId}`);
46
+
47
+ if (savedData) {
48
+ try {
49
+ const formData = JSON.parse(savedData);
50
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
51
+
52
+ // 恢复所有基本输入元素的值
53
+ Object.keys(formData).forEach(inputId => {
54
+ if (inputId.endsWith('_text')) return;
55
+
56
+ const input = iframeDoc.getElementById(inputId) ||
57
+ iframeDoc.querySelector(`[name="${inputId}"]`) ||
58
+ iframeDoc.querySelector(`[placeholder="${inputId}"]`);
59
+
60
+ if (input) {
61
+ input.value = formData[inputId];
62
+
63
+ // 触发change事件
64
+ const event = new Event('change', { bubbles: true });
65
+ input.dispatchEvent(event);
66
+ }
67
+ });
68
+ } catch (e) {
69
+ console.error("恢复表单数据失败:", e);
70
+ }
71
+ }
72
+ }, 500);
73
+ }
74
+
75
+ // 当DOM加载完成时初始化事件监听
76
+ document.addEventListener('DOMContentLoaded', function() {
77
+ // 监听iframe加载完成事件
78
+ const contentFrame = document.getElementById('content');
79
+ if (contentFrame) {
80
+ contentFrame.addEventListener('load', restoreFormInputs);
81
+ }
82
+
83
+ // 在页面切换前保存
84
+ const navLinks = document.querySelectorAll('.sidebar .nav-link');
85
+ navLinks.forEach(link => {
86
+ link.addEventListener('click', saveFormInputs);
87
+ });
88
+
89
+ // 页面离开前保存
90
+ window.addEventListener('beforeunload', saveFormInputs);
91
+ });
92
+ })();