funboost 49.6__py3-none-any.whl → 49.8__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 (31) hide show
  1. funboost/__init__.py +1 -1
  2. funboost/concurrent_pool/async_pool_executor.py +16 -15
  3. funboost/constant.py +19 -0
  4. funboost/consumers/base_consumer.py +30 -8
  5. funboost/consumers/faststream_consumer.py +1 -1
  6. funboost/consumers/http_consumer.py +12 -7
  7. funboost/consumers/kafka_consumer_manually_commit.py +0 -2
  8. funboost/consumers/tcp_consumer.py +11 -10
  9. funboost/consumers/udp_consumer.py +9 -6
  10. funboost/consumers/zeromq_consumer.py +18 -11
  11. funboost/core/exceptions.py +7 -0
  12. funboost/core/func_params_model.py +18 -7
  13. funboost/core/function_result_status_saver.py +15 -0
  14. funboost/core/msg_result_getter.py +51 -1
  15. funboost/core/serialization.py +28 -1
  16. funboost/factories/consumer_factory.py +1 -1
  17. funboost/factories/publisher_factotry.py +1 -1
  18. funboost/funboost_config_deafult.py +2 -2
  19. funboost/function_result_web/__pycache__/app.cpython-39.pyc +0 -0
  20. funboost/publishers/base_publisher.py +16 -2
  21. funboost/publishers/http_publisher.py +7 -1
  22. funboost/publishers/tcp_publisher.py +10 -8
  23. funboost/publishers/udp_publisher.py +8 -6
  24. funboost/publishers/zeromq_publisher.py +5 -1
  25. funboost/timing_job/timing_push.py +3 -1
  26. {funboost-49.6.dist-info → funboost-49.8.dist-info}/METADATA +165 -171
  27. {funboost-49.6.dist-info → funboost-49.8.dist-info}/RECORD +31 -31
  28. {funboost-49.6.dist-info → funboost-49.8.dist-info}/WHEEL +1 -1
  29. {funboost-49.6.dist-info → funboost-49.8.dist-info}/LICENSE +0 -0
  30. {funboost-49.6.dist-info → funboost-49.8.dist-info}/entry_points.txt +0 -0
  31. {funboost-49.6.dist-info → funboost-49.8.dist-info}/top_level.txt +0 -0
funboost/__init__.py CHANGED
@@ -13,7 +13,7 @@ set_frame_config这个模块的 use_config_form_funboost_config_module() 是核
13
13
  这段注释说明和使用的用户无关,只和框架开发人员有关.
14
14
  '''
15
15
 
16
- __version__ = "49.6"
16
+ __version__ = "49.8"
17
17
 
18
18
  from funboost.set_frame_config import show_frame_config
19
19
 
@@ -6,6 +6,7 @@ import threading
6
6
  import time
7
7
  import traceback
8
8
  from threading import Thread
9
+ import traceback
9
10
 
10
11
  from funboost.concurrent_pool.base_pool_type import FunboostBaseConcurrentPool
11
12
  from funboost.core.loggers import FunboostFileLoggerMixin
@@ -53,14 +54,17 @@ class AsyncPoolExecutor(FunboostFileLoggerMixin,FunboostBaseConcurrentPool):
53
54
  使api和线程池一样,最好的性能做法是submit也弄成 async def,生产和消费在同一个线程同一个loop一起运行,但会对调用链路的兼容性产生破坏,从而调用方式不兼容线程池。
54
55
  """
55
56
 
56
- def __init__(self, size, loop=None):
57
+ def __init__(self, size, specify_async_loop=None,
58
+ is_auto_start_specify_async_loop_in_child_thread=True):
57
59
  """
58
60
 
59
61
  :param size: 同时并发运行的协程任务数量。
60
- :param loop:
62
+ :param specify_loop: 可以指定loop,异步三方包的连接池发请求不能使用不同的loop去使用连接池.
61
63
  """
62
64
  self._size = size
63
- self.loop = loop or asyncio.new_event_loop()
65
+ self._specify_async_loop = specify_async_loop
66
+ self._is_auto_start_specify_async_loop_in_child_thread = is_auto_start_specify_async_loop_in_child_thread
67
+ self.loop = specify_async_loop or asyncio.new_event_loop()
64
68
  asyncio.set_event_loop(self.loop)
65
69
  self._diff_init()
66
70
  # self._lock = threading.Lock()
@@ -120,11 +124,16 @@ class AsyncPoolExecutor(FunboostFileLoggerMixin,FunboostBaseConcurrentPool):
120
124
  # self.loop.run_until_complete(asyncio.wait([self._consume() for _ in range(self._size)], loop=self.loop))
121
125
  # self._can_be_closed_flag = True
122
126
  [self.loop.create_task(self._consume()) for _ in range(self._size)]
123
- try:
127
+ if self._specify_async_loop is None:
124
128
  self.loop.run_forever()
125
- except Exception as e:
126
- self.logger.warning(f'{e}') # 如果多个线程使用一个loop,不能重复启动loop,否则会报错。
127
-
129
+ else:
130
+ if self._is_auto_start_specify_async_loop_in_child_thread:
131
+ try:
132
+ self.loop.run_forever() #如果是指定的loop不能多次启动一个loop.
133
+ except Exception as e:
134
+ self.logger.warning(f'{e} {traceback.format_exc()}') # 如果多个线程使用一个loop,不能重复启动loop,否则会报错。
135
+ else:
136
+ pass # 用户需要自己在自己的业务代码中去手动启动loop.run_forever()
128
137
 
129
138
 
130
139
  # def shutdown(self):
@@ -139,14 +148,6 @@ class AsyncPoolExecutor(FunboostFileLoggerMixin,FunboostBaseConcurrentPool):
139
148
 
140
149
 
141
150
 
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
151
  if __name__ == '__main__':
151
152
  def test_async_pool_executor():
152
153
  from funboost.concurrent_pool import CustomThreadPoolExecutor as ThreadPoolExecutor
funboost/constant.py CHANGED
@@ -3,6 +3,25 @@
3
3
 
4
4
 
5
5
  class BrokerEnum:
6
+ """
7
+ 在funboost中万物皆可为消息队列broker,funboost内置了所有 知名的正经经典消息队列作为broker,
8
+ 也支持了基于 内存 各种数据库 文件系统 tcp/udp/http这些socket 模拟作为broker.
9
+ funboost也内置支持了各种python三方包和消费框架作为broker,例如 sqlachemy kombu celery rq dramtiq huey nameko 等等
10
+
11
+ 用户也可以按照文档4.21章节,轻松扩展任何物质概念作为funboost的broker.
12
+ """
13
+
14
+ # funboost框架能轻松兼容消息队列各种工作模式, 拉模式/推模式/轮询模式,单条获取 批量获取
15
+ """
16
+ funboost 的 consumer的 _shedual_task 非常灵活,用户实现把从消息队列取出的消息通过_submit_task方法
17
+ 丢到并发池,他不是强制用户重写实现怎么取一条消息,例如强制你实现一个 _get_one_message的法,
18
+ 那就不灵活和限制扩展任意东西作为broker了,而是用户完全自己来写灵活代码。
19
+ 所以无论获取消息是 拉模式 还是推模式 还是轮询模式,是单条获取 还是多条批量获取,
20
+ 不管你的新中间件和rabbitmq api用法差别有多么巨大,都能轻松扩展任意东西作为funboost的中间件。
21
+ 所以你能看到funboost源码中能轻松实现任物质概念作为funboost的broker。
22
+ """
23
+
24
+
6
25
  EMPTY = 'EMPTY' # 空的实现,需要搭配 boost入参的 consumer_override_cls 和 publisher_override_cls使用,或者被继承。
7
26
 
8
27
  RABBITMQ_AMQPSTORM = 'RABBITMQ_AMQPSTORM' # 使用 amqpstorm 包操作rabbitmq 作为 分布式消息队列,支持消费确认.强烈推荐这个作为funboost中间件。
@@ -37,7 +37,7 @@ from funboost.core.current_task import funboost_current_task, FctContext
37
37
  from funboost.core.loggers import develop_logger
38
38
 
39
39
  from funboost.core.func_params_model import BoosterParams, PublisherParams, BaseJsonAbleModel
40
- from funboost.core.serialization import Serialization
40
+ from funboost.core.serialization import PickleHelper, Serialization
41
41
  from funboost.core.task_id_logger import TaskIdLogger
42
42
  from funboost.constant import FunctionKind
43
43
 
@@ -448,10 +448,10 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
448
448
  """
449
449
  raise NotImplementedError
450
450
 
451
- def convert_msg_before_run(self, msg: typing.Union[str, dict]) -> dict:
451
+ def _convert_msg_before_run(self, msg: typing.Union[str, dict]) -> dict:
452
452
  """
453
453
  转换消息,消息没有使用funboost来发送,并且没有extra相关字段时候
454
- 用户也可以按照4.21文档,继承任意Consumer类,并实现这个方法 convert_msg_before_run,先转换不规范的消息.
454
+ 用户也可以按照4.21文档,继承任意Consumer类,并实现方法 _user_convert_msg_before_run,先转换不规范的消息.
455
455
  """
456
456
  """ 一般消息至少包含这样
457
457
  {
@@ -469,6 +469,7 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
469
469
  extra_params = {'task_id': task_id, 'publish_time': round(time.time(), 4),
470
470
  'publish_time_format': time.strftime('%Y-%m-%d %H:%M:%S')}
471
471
  """
472
+ msg = self._user_convert_msg_before_run(msg)
472
473
  msg = Serialization.to_dict(msg)
473
474
  # 以下是清洗补全字段.
474
475
  if 'extra' not in msg:
@@ -481,10 +482,17 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
481
482
  if 'publish_time_format':
482
483
  extra['publish_time_format'] = MsgGenerater.generate_publish_time_format()
483
484
  return msg
485
+
486
+ def _user_convert_msg_before_run(self, msg: typing.Union[str, dict]) -> dict:
487
+ """
488
+ 用户也可以提前清洗数据
489
+ """
490
+ # print(msg)
491
+ return msg
484
492
 
485
493
  def _submit_task(self, kw):
486
494
 
487
- kw['body'] = self.convert_msg_before_run(kw['body'])
495
+ kw['body'] = self._convert_msg_before_run(kw['body'])
488
496
  self._print_message_get_from_broker(kw['body'])
489
497
  if self._judge_is_daylight():
490
498
  self._requeue(kw)
@@ -635,8 +643,9 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
635
643
  async def aio_user_custom_record_process_info_func(self, current_function_result_status: FunctionResultStatus): # 这个可以继承
636
644
  pass
637
645
 
638
- def _convert_real_function_only_params_by_conusuming_function_kind(self, function_only_params: dict):
646
+ def _convert_real_function_only_params_by_conusuming_function_kind(self, function_only_params: dict,extra_params:dict):
639
647
  """对于实例方法和classmethod 方法, 从消息队列的消息恢复第一个入参, self 和 cls"""
648
+ can_not_json_serializable_keys = extra_params.get('can_not_json_serializable_keys',[])
640
649
  if self.consumer_params.consuming_function_kind in [FunctionKind.CLASS_METHOD, FunctionKind.INSTANCE_METHOD]:
641
650
  real_function_only_params = copy.copy(function_only_params)
642
651
  method_first_param_name = None
@@ -658,8 +667,14 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
658
667
  obj = method_cls(**method_first_param_value[ConstStrForClassMethod.OBJ_INIT_PARAMS])
659
668
  real_function_only_params[method_first_param_name] = obj
660
669
  # print(real_function_only_params)
670
+ if can_not_json_serializable_keys:
671
+ for key in can_not_json_serializable_keys:
672
+ real_function_only_params[key] = PickleHelper.to_obj(real_function_only_params[key])
661
673
  return real_function_only_params
662
674
  else:
675
+ if can_not_json_serializable_keys:
676
+ for key in can_not_json_serializable_keys:
677
+ function_only_params[key] = PickleHelper.to_obj(function_only_params[key])
663
678
  return function_only_params
664
679
 
665
680
  # noinspection PyProtectedMember
@@ -758,7 +773,7 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
758
773
  self.logger.warning(f'取消运行 {task_id} {function_only_params}')
759
774
  return function_result_status
760
775
  function_run = kill_remote_task.kill_fun_deco(task_id)(function_run) # 用杀死装饰器包装起来在另一个线程运行函数,以便等待远程杀死。
761
- function_result_status.result = function_run(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params))
776
+ function_result_status.result = function_run(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params,kw['body']['extra']))
762
777
  # if asyncio.iscoroutine(function_result_status.result):
763
778
  # log_msg = f'''异步的协程消费函数必须使用 async 并发模式并发,请设置消费函数 {self.consuming_function.__name__} 的concurrent_mode 为 ConcurrentModeEnum.ASYNC 或 4'''
764
779
  # # self.logger.critical(msg=f'{log_msg} \n')
@@ -813,6 +828,9 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
813
828
  self.logger.error(msg=log_msg, exc_info=self._get_priority_conf(kw, 'is_print_detail_exception'))
814
829
  # traceback.print_exc()
815
830
  function_result_status.exception = f'{e.__class__.__name__} {str(e)}'
831
+ function_result_status.exception_msg = str(e)
832
+ function_result_status.exception_type = e.__class__.__name__
833
+
816
834
  function_result_status.result = FunctionResultStatus.FUNC_RUN_ERROR
817
835
  return function_result_status
818
836
 
@@ -899,7 +917,7 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
899
917
  logger=self.logger,queue_name=self.queue_name,)
900
918
  fct.set_fct_context(fct_context)
901
919
  try:
902
- corotinue_obj = self.consuming_function(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params))
920
+ corotinue_obj = self.consuming_function(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params,kw['body']['extra']))
903
921
  if not asyncio.iscoroutine(corotinue_obj):
904
922
  log_msg = f'''当前设置的并发模式为 async 并发模式,但消费函数不是异步协程函数,请不要把消费函数 {self.consuming_function.__name__} 的 concurrent_mode 设置错误'''
905
923
  # self.logger.critical(msg=f'{log_msg} \n')
@@ -946,6 +964,8 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
946
964
  # self.error_file_logger.error(msg=f'{log_msg} \n', exc_info=self._get_priority_conf(kw, 'is_print_detail_exception'))
947
965
  self.logger.error(msg=log_msg, exc_info=self._get_priority_conf(kw, 'is_print_detail_exception'))
948
966
  function_result_status.exception = f'{e.__class__.__name__} {str(e)}'
967
+ function_result_status.exception_msg = str(e)
968
+ function_result_status.exception_type = e.__class__.__name__
949
969
  function_result_status.result = FunctionResultStatus.FUNC_RUN_ERROR
950
970
  return function_result_status
951
971
 
@@ -1106,7 +1126,9 @@ class ConcurrentModeDispatcher(FunboostFileLoggerMixin):
1106
1126
  # pool_type = ProcessPoolExecutor
1107
1127
  if self._concurrent_mode == ConcurrentModeEnum.ASYNC:
1108
1128
  self.consumer._concurrent_pool = self.consumer.consumer_params.specify_concurrent_pool or pool_type(
1109
- self.consumer.consumer_params.concurrent_num, loop=self.consumer.consumer_params.specify_async_loop)
1129
+ self.consumer.consumer_params.concurrent_num,
1130
+ specify_async_loop=self.consumer.consumer_params.specify_async_loop,
1131
+ is_auto_start_specify_async_loop_in_child_thread=self.consumer.consumer_params.is_auto_start_specify_async_loop_in_child_thread)
1110
1132
  else:
1111
1133
  # print(pool_type)
1112
1134
  self.consumer._concurrent_pool = self.consumer.consumer_params.specify_concurrent_pool or pool_type(self.consumer.consumer_params.concurrent_num)
@@ -22,7 +22,7 @@ class FastStreamConsumer(EmptyConsumer):
22
22
  # print(logger.name)
23
23
  # return self.consuming_function(*args, **kwargs) # 如果没有声明 autoretry_for ,那么消费函数出错了就不会自动重试了。
24
24
  # print(msg)
25
- function_only_params = delete_keys_and_return_new_dict(json.loads(msg))
25
+ function_only_params = delete_keys_and_return_new_dict(Serialization.to_dict(msg))
26
26
  if self._consuming_function_is_asyncio:
27
27
  result = await self.consuming_function(**function_only_params)
28
28
  else:
@@ -10,20 +10,25 @@ import json
10
10
  from funboost.consumers.base_consumer import AbstractConsumer
11
11
  from funboost.core.lazy_impoter import AioHttpImporter
12
12
 
13
+
13
14
  class HTTPConsumer(AbstractConsumer, ):
14
15
  """
15
16
  http 实现消息队列,不支持持久化,但不需要安装软件。
16
17
  """
17
-
18
+ BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'host': '127.0.0.1', 'port': None}
18
19
 
19
20
  # noinspection PyAttributeOutsideInit
20
21
  def custom_init(self):
21
- try:
22
- self._ip, self._port = self.queue_name.split(':')
23
- self._port = int(self._port)
24
- except BaseException as e:
25
- self.logger.critical(f'http作为消息队列时候,队列名字必须设置为 例如 192.168.1.101:8200 这种, ip:port')
26
- raise e
22
+ # try:
23
+ # self._ip, self._port = self.queue_name.split(':')
24
+ # self._port = int(self._port)
25
+ # except BaseException as e:
26
+ # self.logger.critical(f'http作为消息队列时候,队列名字必须设置为 例如 192.168.1.101:8200 这种, ip:port')
27
+ # raise e
28
+ self._ip = self.consumer_params.broker_exclusive_config['host']
29
+ self._port = self.consumer_params.broker_exclusive_config['port']
30
+ if self._port is None:
31
+ raise ValueError('please specify port')
27
32
 
28
33
  # noinspection DuplicatedCode
29
34
  def _shedual_task(self):
@@ -72,8 +72,6 @@ class KafkaConsumerManuallyCommit(AbstractConsumer):
72
72
  f'从kafka的 [{self._queue_name}] 主题,分区 {msg.partition()} 中 的 offset {msg.offset()} 取出的消息是: {msg.value()}') # noqa
73
73
  self._submit_task(kw)
74
74
 
75
- # kw = {'consumer': consumer, 'message': message, 'body': json.loads(message.value)}
76
- # self._submit_task(kw)
77
75
 
78
76
  def _manually_commit(self):
79
77
  """
@@ -13,32 +13,34 @@ class TCPConsumer(AbstractConsumer, ):
13
13
  socket 实现消息队列,不支持持久化,但不需要安装软件。
14
14
  """
15
15
 
16
-
17
- BUFSIZE = 10240
16
+ BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'host': '127.0.0.1', 'port': None, 'bufsize': 10240}
18
17
 
19
18
  # noinspection PyAttributeOutsideInit
20
19
  def custom_init(self):
21
- ip__port_str = self.queue_name.split(':')
22
- ip_port = (ip__port_str[0], int(ip__port_str[1]))
23
- self._ip_port_raw = ip_port
24
- self._ip_port = ('', ip_port[1])
20
+ # ip__port_str = self.queue_name.split(':')
21
+ # ip_port = (ip__port_str[0], int(ip__port_str[1]))
22
+ # self._ip_port_raw = ip_port
23
+ # self._ip_port = ('', ip_port[1])
25
24
  # ip_port = ('', 9999)
25
+ self._ip_port = (self.consumer_params.broker_exclusive_config['host'],
26
+ self.consumer_params.broker_exclusive_config['port'])
27
+ self.bufsize = self.consumer_params.broker_exclusive_config['bufsize']
26
28
 
27
29
  # noinspection DuplicatedCode
28
30
  def _shedual_task(self):
29
- """ tcp为消息队列中间件 时候 queue_name 要设置为例如 127.0.0.1:5689"""
31
+
30
32
  server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # tcp协议
31
33
  server.bind(self._ip_port)
32
34
  server.listen(128)
33
35
  self._server = server
34
36
  while True:
35
37
  tcp_cli_sock, addr = self._server.accept()
36
- Thread(target=self.__handle_conn, args=(tcp_cli_sock,)).start() # 服务端多线程,可以同时处理多个tcp长链接客户端发来的消息。
38
+ Thread(target=self.__handle_conn, args=(tcp_cli_sock,)).start() # 服务端多线程,可以同时处理多个tcp长链接客户端发来的消息。
37
39
 
38
40
  def __handle_conn(self, tcp_cli_sock):
39
41
  try:
40
42
  while True:
41
- data = tcp_cli_sock.recv(self.BUFSIZE)
43
+ data = tcp_cli_sock.recv(self.bufsize)
42
44
  # print('server收到的数据', data)
43
45
  if not data:
44
46
  break
@@ -56,4 +58,3 @@ class TCPConsumer(AbstractConsumer, ):
56
58
 
57
59
  def _requeue(self, kw):
58
60
  pass
59
-
@@ -12,14 +12,17 @@ class UDPConsumer(AbstractConsumer, ):
12
12
  socket 实现消息队列,不支持持久化,但不需要安装软件。
13
13
  """
14
14
 
15
- BUFSIZE = 10240
15
+ BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'host': '127.0.0.1', 'port': None, 'bufsize': 10240}
16
16
 
17
17
  # noinspection PyAttributeOutsideInit
18
18
  def custom_init(self):
19
- """ udp为消息队列中间件 时候 queue_name 要设置为例如 127.0.0.1:5689"""
19
+
20
20
  self.__udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
21
- ip__port_str = self.queue_name.split(':')
22
- self.__ip_port = (ip__port_str[0], int(ip__port_str[1]))
21
+ # ip__port_str = self.queue_name.split(':')
22
+ # self.__ip_port = (ip__port_str[0], int(ip__port_str[1]))
23
+ self.__ip_port = (self.consumer_params.broker_exclusive_config['host'],
24
+ self.consumer_params.broker_exclusive_config['port'])
25
+ self._bufsize = self.consumer_params.broker_exclusive_config['bufsize']
23
26
  self.__udp_client.connect(self.__ip_port)
24
27
 
25
28
  # noinspection DuplicatedCode
@@ -29,7 +32,7 @@ class UDPConsumer(AbstractConsumer, ):
29
32
  server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp协议
30
33
  server.bind(ip_port)
31
34
  while True:
32
- data, client_addr = server.recvfrom(self.BUFSIZE)
35
+ data, client_addr = server.recvfrom(self._bufsize)
33
36
  # print('server收到的数据', data)
34
37
  # self._print_message_get_from_broker(f'udp {ip_port}', data.decode())
35
38
  server.sendto('has_recived'.encode(), client_addr)
@@ -41,4 +44,4 @@ class UDPConsumer(AbstractConsumer, ):
41
44
 
42
45
  def _requeue(self, kw):
43
46
  self.__udp_client.send(json.dumps(kw['body']).encode())
44
- data = self.__udp_client.recv(self.BUFSIZE)
47
+ data = self.__udp_client.recv(self._bufsize)
@@ -67,31 +67,38 @@ class ZeroMqConsumer(AbstractConsumer):
67
67
  zeromq 中间件的消费者,zeromq基于socket代码,不会持久化,且不需要安装软件。
68
68
  """
69
69
 
70
- def start_broker_queue_name_as_port(self):
70
+ BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'port': None}
71
+
72
+ def custom_init(self):
73
+ self._port = self.consumer_params.broker_exclusive_config['port']
74
+ if self._port is None:
75
+ raise ValueError('please specify port')
76
+
77
+ def _start_broker_port(self):
71
78
  # threading.Thread(target=self._start_broker).start()
72
79
  # noinspection PyBroadException
73
80
  try:
74
- if not (10000 < int(self._queue_name) < 65535):
75
- raise ValueError(",请设置queue的名字是一个 10000到65535的之间的一个端口数字")
81
+ if not (10000 < int(self._port) < 65535):
82
+ raise ValueError("请设置port是一个 10000到65535的之间的一个端口数字")
76
83
  except BaseException:
77
- self.logger.critical(f" zeromq 模式以 queue 的民资作为tcp 端口,请设置queue的名字是一个 10000 65535 之间的一个端口数字")
84
+ self.logger.critical(f" 请设置port是一个 10000到65535的之间的一个端口数字")
78
85
  # noinspection PyProtectedMember
79
86
  os._exit(444)
80
- if check_port_is_used('127.0.0.1', int(self._queue_name)):
81
- self.logger.debug(f"""{int(self._queue_name)} router端口已经启动(或占用) """)
87
+ if check_port_is_used('127.0.0.1', int(self._port)):
88
+ self.logger.debug(f"""{int(self._port)} router端口已经启动(或占用) """)
82
89
  return
83
- if check_port_is_used('127.0.0.1', int(self._queue_name) + 1):
84
- self.logger.debug(f"""{int(self._queue_name) + 1} dealer 端口已经启动(或占用) """)
90
+ if check_port_is_used('127.0.0.1', int(self._port) + 1):
91
+ self.logger.debug(f"""{int(self._port) + 1} dealer 端口已经启动(或占用) """)
85
92
  return
86
- multiprocessing.Process(target=start_broker, args=(int(self._queue_name), int(self._queue_name) + 1)).start()
93
+ multiprocessing.Process(target=start_broker, args=(int(self._port), int(self._port) + 1)).start()
87
94
 
88
95
  # noinspection DuplicatedCode
89
96
  def _shedual_task(self):
90
- self.start_broker_queue_name_as_port()
97
+ self._start_broker_port()
91
98
  context = ZmqImporter().zmq.Context()
92
99
  # noinspection PyUnresolvedReferences
93
100
  zsocket = context.socket(ZmqImporter().zmq.REP)
94
- zsocket.connect(f"tcp://localhost:{int(self._queue_name) + 1}")
101
+ zsocket.connect(f"tcp://localhost:{int(self._port) + 1}")
95
102
 
96
103
  while True:
97
104
  message = zsocket.recv()
@@ -11,6 +11,11 @@ class ExceptionForRetry(FunboostException):
11
11
  class ExceptionForRequeue(FunboostException):
12
12
  """框架检测到此错误,重新放回当前队列中"""
13
13
 
14
+ class FunboostWaitRpcResultTimeout(FunboostException):
15
+ """等待rpc结果超过了指定时间"""
16
+
17
+ class FunboostRpcResultError(FunboostException):
18
+ """rpc结果是错误状态"""
14
19
 
15
20
  class ExceptionForPushToDlxqueue(FunboostException):
16
21
  """框架检测到ExceptionForPushToDlxqueue错误,发布到死信队列"""
@@ -40,3 +45,5 @@ def f(x):
40
45
 
41
46
  def __str__(self):
42
47
  return self.new_version_change_hint
48
+
49
+
@@ -109,8 +109,8 @@ class FunctionResultStatusPersistanceConfig(BaseJsonAbleModel):
109
109
  flogger.warning(f'你设置的过期时间为 {value} ,设置的时间过长。 ')
110
110
  return value
111
111
 
112
- @root_validator(skip_on_failure=True,allow_reuse=True)
113
- def cehck_values(cls, values: dict):
112
+ @root_validator(skip_on_failure=True)
113
+ def check_values(cls, values: dict):
114
114
  if not values['is_save_status'] and values['is_save_result']:
115
115
  raise ValueError(f'你设置的是不保存函数运行状态但保存函数运行结果。不允许你这么设置')
116
116
  return values
@@ -134,7 +134,9 @@ class BoosterParams(BaseJsonAbleModel):
134
134
  concurrent_mode: str = ConcurrentModeEnum.THREADING # 并发模式,支持THREADING,GEVENT,EVENTLET,ASYNC,SINGLE_THREAD并发,multi_process_consume 支持协程/线程 叠加多进程并发,性能炸裂.
135
135
  concurrent_num: int = 50 # 并发数量,并发种类由concurrent_mode决定
136
136
  specify_concurrent_pool: typing.Optional[FunboostBaseConcurrentPool] = None # 使用指定的线程池/携程池,可以多个消费者共使用一个线程池,节约线程.不为None时候。threads_num失效
137
+
137
138
  specify_async_loop: typing.Optional[asyncio.AbstractEventLoop] = None # 指定的async的loop循环,设置并发模式为async才能起作用。 有些包例如aiohttp,发送请求和httpclient的实例化不能处在两个不同的loop中,可以传过来.
139
+ is_auto_start_specify_async_loop_in_child_thread: bool = True # 是否在子线程中自动启动指定的async的loop循环,设置并发模式为async才能起作用。如果是False,用户自己在自己的代码中去手动启动自己的loop.run_forever()
138
140
 
139
141
  """qps:
140
142
  强悍的控制功能,指定1秒内的函数执行次数,例如可以是小数0.01代表每100秒执行一次,也可以是50代表1秒执行50次.为None则不控频。 设置qps时候,不需要指定并发数量,funboost的能够自适应智能动态调节并发池大小."""
@@ -159,10 +161,18 @@ class BoosterParams(BaseJsonAbleModel):
159
161
  consumin_function_decorator: typing.Optional[typing.Callable] = None # 函数的装饰器。因为此框架做参数自动转指点,需要获取精准的入参名称,不支持在消费函数上叠加 @ *args **kwargs的装饰器,如果想用装饰器可以这里指定。
160
162
  function_timeout: typing.Union[int, float,None] = None # 超时秒数,函数运行超过这个时间,则自动杀死函数。为0是不限制。 谨慎使用,非必要别去设置超时时间,设置后性能会降低(因为需要把用户函数包装到另一个线单独的程中去运行),而且突然强制超时杀死运行中函数,可能会造成死锁.(例如用户函数在获得线程锁后突然杀死函数,别的线程再也无法获得锁了)
161
163
 
162
- log_level: int = logging.DEBUG # 消费者和发布者的日志级别,建议设置DEBUG级别,不然无法知道正在运行什么消息
164
+ """
165
+ log_level:
166
+ logger_name 对应的 日志级别
167
+ 消费者和发布者的日志级别,建议设置DEBUG级别,不然无法知道正在运行什么消息.
168
+ 这个是funboost每个队列的单独命名空间的日志级别,丝毫不会影响改变用户其他日志以及root命名空间的日志级别,所以DEBUG级别就好,
169
+ 用户不要压根不懂什么是python logger 的name,还去手痒调高级别.
170
+ 不懂python日志命名空间的小白去看nb_log文档,或者直接问 ai大模型 python logger name的作用是什么.
171
+ """
172
+ log_level: int = logging.DEBUG # 不需要改这个级别,请看上面原因
163
173
  logger_prefix: str = '' # 日志名字前缀,可以设置前缀
164
174
  create_logger_file: bool = True # 发布者和消费者是否创建文件文件日志,为False则只打印控制台不写文件.
165
- logger_name: str = '' # 队列消费者发布者的日志命名空间.
175
+ logger_name: typing.Union[str, None] = '' # 队列消费者发布者的日志命名空间.
166
176
  log_filename: typing.Union[str, None] = None # 消费者发布者的文件日志名字.如果为None,则自动使用 funboost.队列 名字作为文件日志名字. 日志文件夹是在nb_log_config.py的 LOG_PATH中决定的.
167
177
  is_show_message_get_from_broker: bool = False # 运行时候,是否记录从消息队列获取出来的消息内容
168
178
  is_print_detail_exception: bool = True # 消费函数出错时候,是否打印详细的报错堆栈,为False则只打印简略的报错信息不包含堆栈.
@@ -221,7 +231,7 @@ class BoosterParams(BaseJsonAbleModel):
221
231
 
222
232
 
223
233
 
224
- @root_validator(skip_on_failure=True)
234
+ @root_validator(skip_on_failure=True, )
225
235
  def check_values(cls, values: dict):
226
236
 
227
237
 
@@ -307,7 +317,7 @@ class PriorityConsumingControlConfig(BaseJsonAbleModel):
307
317
  eta: typing.Union[datetime.datetime, str,None] = None # 时间对象, 或 %Y-%m-%d %H:%M:%S 字符串。
308
318
  misfire_grace_time: typing.Union[int, None] = None
309
319
 
310
- other_extra_params: dict = None # 其他参数, 例如消息优先级 , priority_control_config=PriorityConsumingControlConfig(other_extra_params={'priroty': priorityxx}),
320
+ other_extra_params: typing.Optional[dict] = None # 其他参数, 例如消息优先级 , priority_control_config=PriorityConsumingControlConfig(other_extra_params={'priroty': priorityxx}),
311
321
 
312
322
  """filter_str:
313
323
  用户指定过滤字符串, 例如函数入参是 def fun(userid,username,sex,user_description),
@@ -318,6 +328,7 @@ class PriorityConsumingControlConfig(BaseJsonAbleModel):
318
328
  """
319
329
  filter_str :typing.Optional[str] = None
320
330
 
331
+ can_not_json_serializable_keys: typing.List[str] = None # 不能json序列化的入参名字,反序列化时候需要使用pickle来反序列化这些字段(这个是自动生成的,用户不需要手动指定此入参。)
321
332
  @root_validator(skip_on_failure=True)
322
333
  def cehck_values(cls, values: dict):
323
334
  if values['countdown'] and values['eta']:
@@ -356,4 +367,4 @@ if __name__ == '__main__':
356
367
  # print(PriorityConsumingControlConfig().get_str_dict())
357
368
 
358
369
  print(BoosterParams(queue_name='3213', specify_concurrent_pool=FlexibleThreadPool(100)).json_pre())
359
- print(PublisherParams.schema_json())
370
+ # print(PublisherParams.schema_json()) # 注释掉,因为 PublisherParams 包含 Callable 类型字段,无法生成 JSON Schema
@@ -51,6 +51,9 @@ class FunctionResultStatus():
51
51
  self.result = None
52
52
  self.run_times = 0
53
53
  self.exception = None
54
+ self.exception_type = None
55
+ self.exception_msg = None
56
+ self.rpc_chain_error_msg_dict:dict = None
54
57
  self.time_start = time.time()
55
58
  self.time_cost = None
56
59
  self.time_end = None
@@ -62,6 +65,15 @@ class FunctionResultStatus():
62
65
  self._has_kill_task = False
63
66
  self.rpc_result_expire_seconds = None
64
67
 
68
+ @classmethod
69
+ def parse_status_and_result_to_obj(cls,status_dict:dict):
70
+ obj = cls(status_dict['queue_name'],status_dict['function'],status_dict['msg_dict'])
71
+ for k,v in status_dict.items():
72
+ # if k.startswith('_'):
73
+ # continue
74
+ setattr(obj,k,v)
75
+ return obj
76
+
65
77
  def get_status_dict(self, without_datetime_obj=False):
66
78
  self.time_end = time.time()
67
79
  if self.run_status == RunStatus.running:
@@ -102,6 +114,9 @@ class FunctionResultStatus():
102
114
  def __str__(self):
103
115
  return f'''{self.__class__} {Serialization.to_json_str(self.get_status_dict())}'''
104
116
 
117
+ def to_pretty_json_str(self):
118
+ return json.dumps(self.get_status_dict(),indent=4,ensure_ascii=False)
119
+
105
120
 
106
121
  class ResultPersistenceHelper(MongoMixin, FunboostFileLoggerMixin):
107
122
  TASK_STATUS_DB = 'task_status'
@@ -12,13 +12,29 @@ from funboost.utils.redis_manager import RedisMixin
12
12
  from funboost.utils.redis_manager import AioRedisMixin
13
13
  from funboost.core.serialization import Serialization
14
14
 
15
+ from funboost.core.function_result_status_saver import FunctionResultStatus
16
+
15
17
  class HasNotAsyncResult(Exception):
16
18
  pass
17
19
 
18
20
 
19
21
  NO_RESULT = 'no_result'
20
22
 
21
-
23
+ def _judge_rpc_function_result_status_obj(status_and_result_obj:FunctionResultStatus,raise_exception:bool):
24
+ if status_and_result_obj is None:
25
+ raise FunboostWaitRpcResultTimeout(f'等待 {status_and_result_obj.task_id} rpc结果超过了指定时间')
26
+ if status_and_result_obj.success is True:
27
+ return status_and_result_obj
28
+ else:
29
+ raw_erorr = status_and_result_obj.exception
30
+ if status_and_result_obj.exception_type == 'FunboostRpcResultError':
31
+ raw_erorr = json.loads(status_and_result_obj.exception_msg) # 使canvas链式报错json显示更美观
32
+ error_msg_dict = {'task_id':status_and_result_obj.task_id,'raw_error':raw_erorr}
33
+ if raise_exception:
34
+ raise FunboostRpcResultError(json.dumps(error_msg_dict,indent=4,ensure_ascii=False))
35
+ else:
36
+ status_and_result_obj.rpc_chain_error_msg_dict = error_msg_dict
37
+ return status_and_result_obj
22
38
  class AsyncResult(RedisMixin):
23
39
  default_callback_run_executor = FlexibleThreadPoolMinWorkers0(200,work_queue_maxsize=50)
24
40
 
@@ -63,6 +79,14 @@ class AsyncResult(RedisMixin):
63
79
  return self._status_and_result
64
80
  return None
65
81
  return self._status_and_result
82
+
83
+ @property
84
+ def status_and_result_obj(self) -> FunctionResultStatus:
85
+ """这个是为了比字典有更好的ide代码补全效果"""
86
+ if self.status_and_result is not None:
87
+ return FunctionResultStatus.parse_status_and_result_to_obj(self.status_and_result)
88
+
89
+ rpc_data =status_and_result_obj
66
90
 
67
91
  def get(self):
68
92
  # print(self.status_and_result)
@@ -104,6 +128,14 @@ class AsyncResult(RedisMixin):
104
128
  async_result.set_callback(show_result) # 使用回调函数在线程池中并发的运行函数结果
105
129
  '''
106
130
  self.callback_run_executor.submit(self._run_callback_func, callback_func)
131
+
132
+ def wait_rpc_data_or_raise(self,raise_exception:bool=True)->FunctionResultStatus:
133
+ return _judge_rpc_function_result_status_obj(self.status_and_result_obj,raise_exception)
134
+
135
+ @classmethod
136
+ def batch_wait_rpc_data_or_raise(cls,r_list:typing.List['AsyncResult'],raise_exception:bool=True)->typing.List[FunctionResultStatus]:
137
+ return [ _judge_rpc_function_result_status_obj(r.status_and_result_obj,raise_exception)
138
+ for r in r_list]
107
139
 
108
140
 
109
141
  class AioAsyncResult(AioRedisMixin):
@@ -169,6 +201,14 @@ if __name__ == '__main__':
169
201
  return None
170
202
  return self._status_and_result
171
203
 
204
+ @property
205
+ async def status_and_result_obj(self) -> FunctionResultStatus:
206
+ """这个是为了比字典有更好的ide代码补全效果"""
207
+ sr = await self.status_and_result
208
+ if sr is not None:
209
+ return FunctionResultStatus.parse_status_and_result_to_obj(sr)
210
+
211
+ rpc_data =status_and_result_obj
172
212
  async def get(self):
173
213
  # print(self.status_and_result)
174
214
  if (await self.status_and_result) is not None:
@@ -192,6 +232,16 @@ if __name__ == '__main__':
192
232
  async def set_callback(self, aio_callback_func: typing.Callable):
193
233
  asyncio.create_task(self._run_callback_func(callback_func=aio_callback_func))
194
234
 
235
+ async def wait_rpc_data_or_raise(self,raise_exception:bool=True)->FunctionResultStatus:
236
+ return _judge_rpc_function_result_status_obj(await self.status_and_result_obj,raise_exception)
237
+
238
+ @classmethod
239
+ async def batch_wait_rpc_data_or_raise(cls,r_list:typing.List['AioAsyncResult'],raise_exception:bool=True)->typing.List[FunctionResultStatus]:
240
+ return [ _judge_rpc_function_result_status_obj(await r.status_and_result_obj,raise_exception)
241
+ for r in r_list]
242
+
243
+
244
+
195
245
 
196
246
  class ResultFromMongo(MongoMixin):
197
247
  """