sycommon-python-lib 0.1.1__py3-none-any.whl → 0.1.3__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 sycommon-python-lib might be problematic. Click here for more details.

sycommon/services.py CHANGED
@@ -1,29 +1,217 @@
1
- from typing import Callable, List, Tuple
2
-
1
+ from typing import Any, Callable, Dict, List, Tuple, Union, Optional, Awaitable
2
+ import asyncio
3
+ import logging
4
+ from contextlib import asynccontextmanager
5
+ from dotenv import load_dotenv
6
+ from fastapi import FastAPI
7
+ from pydantic import BaseModel
8
+ import yaml
3
9
  from sycommon.config.Config import SingletonMeta
10
+ from sycommon.models.mqlistener_config import RabbitMQListenerConfig
11
+ from sycommon.models.mqsend_config import RabbitMQSendConfig
12
+ from sycommon.rabbitmq.rabbitmq_service import RabbitMQService
4
13
 
5
14
 
6
15
  class Services(metaclass=SingletonMeta):
16
+ _loop: Optional[asyncio.AbstractEventLoop] = None
17
+ _config: Optional[dict] = None
18
+ _initialized: bool = False
19
+ _registered_senders: List[str] = []
20
+ _mq_tasks: List[asyncio.Task] = []
21
+ _pending_setup: Optional[Callable[..., Awaitable[None]]] = None
22
+ _instance: Optional['Services'] = None
23
+
7
24
  def __init__(self, config):
8
- self.config = config
25
+ if not Services._config:
26
+ Services._config = config
27
+ Services._instance = self
28
+
29
+ @classmethod
30
+ def get_lifespan(cls, config):
31
+ """返回 FastAPI 的 lifespan 管理器"""
32
+ cls._config = config
33
+
34
+ @asynccontextmanager
35
+ async def lifespan(app):
36
+ # 应用启动时初始化
37
+ cls._loop = asyncio.get_running_loop()
38
+ cls._initialized = True
39
+ logging.info("Services initialized with FastAPI event loop")
40
+
41
+ # 执行之前缓存的MQ设置
42
+ if cls._pending_setup:
43
+ try:
44
+ await cls._pending_setup()
45
+ except Exception as e:
46
+ logging.error(f"执行MQ初始化失败: {str(e)}", exc_info=True)
47
+ finally:
48
+ cls._pending_setup = None
49
+
50
+ try:
51
+ yield
52
+ finally:
53
+ # 应用关闭时清理
54
+ await cls.shutdown()
55
+ logging.info("Services shutdown completed")
9
56
 
10
- def plugins(self, nacos_service=None, logging_service=None, database_service: Tuple[Callable, str] | List[Tuple[Callable, str]] = None):
11
- # 注册nacos服务
57
+ return lifespan
58
+
59
+ @classmethod
60
+ def plugins(cls,
61
+ middleware: Optional[Tuple[Callable, FastAPI]] = None,
62
+ nacos_service: Optional[Callable] = None,
63
+ logging_service: Optional[Callable] = None,
64
+ database_service: Optional[Union[Tuple[Callable, str],
65
+ List[Tuple[Callable, str]]]] = None,
66
+ rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
67
+ rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
68
+ ) -> None:
69
+ """
70
+ 类方法:注册各种服务插件
71
+ 确保单例实例存在后调用实例方法的register_plugins
72
+ """
73
+ # 确保实例已创建
74
+ if not cls._instance:
75
+ if not cls._config:
76
+ raise ValueError("Services尚未初始化,请先提供配置")
77
+ cls._instance = Services(cls._config)
78
+
79
+ cls._instance.register_plugins(
80
+ middleware=middleware,
81
+ nacos_service=nacos_service,
82
+ logging_service=logging_service,
83
+ database_service=database_service,
84
+ rabbitmq_listeners=rabbitmq_listeners,
85
+ rabbitmq_senders=rabbitmq_senders
86
+ )
87
+
88
+ def register_plugins(
89
+ self,
90
+ middleware: Optional[Tuple[Callable, FastAPI]] = None,
91
+ nacos_service: Optional[Callable] = None,
92
+ logging_service: Optional[Callable] = None,
93
+ database_service: Optional[Union[Tuple[Callable,
94
+ str], List[Tuple[Callable, str]]]] = None,
95
+ rabbitmq_listeners: Optional[List[RabbitMQListenerConfig]] = None,
96
+ rabbitmq_senders: Optional[List[RabbitMQSendConfig]] = None
97
+ ) -> None:
98
+ """实例方法:实际执行各种服务插件的注册逻辑"""
99
+ # 注册非异步服务
100
+ if middleware:
101
+ self._setup_middleware(middleware)
12
102
  if nacos_service:
13
- nacos_service(self.config)
14
- # 注册日志服务
103
+ nacos_service(self._config)
15
104
  if logging_service:
16
- logging_service(self.config)
17
- # 注册数据库服务
105
+ logging_service(self._config)
18
106
  if database_service:
19
- if isinstance(database_service, tuple):
20
- # 单个数据库服务
21
- db_setup, db_name = database_service
22
- db_setup(self.config, db_name)
23
- elif isinstance(database_service, list):
24
- # 多个数据库服务
25
- for db_setup, db_name in database_service:
26
- db_setup(self.config, db_name)
27
- else:
28
- raise TypeError(
29
- "database_service must be a tuple or a list of tuples")
107
+ self._setup_database(database_service)
108
+
109
+ RabbitMQService.init(self._config)
110
+
111
+ # MQ设置异步函数
112
+ async def setup_mq_components():
113
+ if rabbitmq_senders:
114
+ self._setup_senders(rabbitmq_senders)
115
+ if rabbitmq_listeners:
116
+ self._setup_listeners(rabbitmq_listeners)
117
+
118
+ # 存储到_pending_setup,等待lifespan异步执行
119
+ Services._pending_setup = setup_mq_components
120
+
121
+ def _setup_database(self, database_service):
122
+ if isinstance(database_service, tuple):
123
+ db_setup, db_name = database_service
124
+ db_setup(self._config, db_name)
125
+ elif isinstance(database_service, list):
126
+ for db_setup, db_name in database_service:
127
+ db_setup(self._config, db_name)
128
+
129
+ def _setup_middleware(self, middleware):
130
+ if isinstance(middleware, tuple):
131
+ middleware_setup, app = middleware
132
+ middleware_setup(app, self._config)
133
+
134
+ def _setup_senders(self, rabbitmq_senders):
135
+ Services._registered_senders = [
136
+ sender.queue_name for sender in rabbitmq_senders]
137
+ if self._loop: # 确保loop存在
138
+ task = self._loop.create_task(
139
+ self._setup_and_wait(
140
+ RabbitMQService.setup_senders, rabbitmq_senders)
141
+ )
142
+ self._mq_tasks.append(task)
143
+ logging.info(f"已注册的RabbitMQ发送器: {Services._registered_senders}")
144
+
145
+ def _setup_listeners(self, rabbitmq_listeners):
146
+ if self._loop: # 确保loop存在
147
+ task = self._loop.create_task(
148
+ self._setup_and_wait(
149
+ RabbitMQService.setup_listeners, rabbitmq_listeners)
150
+ )
151
+ self._mq_tasks.append(task)
152
+
153
+ async def _setup_and_wait(self, setup_func, *args, **kwargs):
154
+ try:
155
+ await setup_func(*args, **kwargs)
156
+ except Exception as e:
157
+ logging.error(
158
+ f"Error in {setup_func.__name__}: {str(e)}", exc_info=True)
159
+
160
+ @classmethod
161
+ async def send_message(
162
+ cls,
163
+ queue_name: str,
164
+ data: Union[str, Dict[str, Any], BaseModel, None],
165
+ max_retries: int = 3, # 最大重试次数
166
+ retry_delay: float = 1.0, # 重试间隔(秒)
167
+ **kwargs
168
+ ) -> None:
169
+ """发送消息,添加重试机制处理发送器不存在的情况"""
170
+ if not cls._initialized or not cls._loop:
171
+ logging.error("Services not properly initialized!")
172
+ raise ValueError("服务未正确初始化")
173
+
174
+ # 重试逻辑
175
+ for attempt in range(max_retries):
176
+ try:
177
+ # 检查发送器是否已注册
178
+ if queue_name not in cls._registered_senders:
179
+ # 可能是初始化尚未完成,尝试刷新注册列表
180
+ cls._registered_senders = RabbitMQService.sender_client_names
181
+ if queue_name not in cls._registered_senders:
182
+ raise ValueError(f"发送器 {queue_name} 未注册")
183
+
184
+ # 获取发送器
185
+ sender = RabbitMQService.get_sender(queue_name)
186
+ if not sender:
187
+ raise ValueError(f"发送器 '{queue_name}' 不存在")
188
+
189
+ # 发送消息
190
+ await RabbitMQService.send_message(data, queue_name, ** kwargs)
191
+ logging.info(f"消息发送成功(尝试 {attempt+1}/{max_retries})")
192
+ return # 成功发送,退出函数
193
+
194
+ except Exception as e:
195
+ # 最后一次尝试失败则抛出异常
196
+ if attempt == max_retries - 1:
197
+ logging.error(
198
+ f"消息发送失败(已尝试 {max_retries} 次): {str(e)}", exc_info=True)
199
+ raise
200
+
201
+ # 非最后一次尝试,记录警告并等待重试
202
+ logging.warning(
203
+ f"消息发送失败(尝试 {attempt+1}/{max_retries}): {str(e)},"
204
+ f"{retry_delay}秒后重试..."
205
+ )
206
+ await asyncio.sleep(retry_delay)
207
+
208
+ @staticmethod
209
+ async def shutdown():
210
+ for task in Services._mq_tasks:
211
+ if not task.done():
212
+ task.cancel()
213
+ try:
214
+ await task
215
+ except asyncio.CancelledError:
216
+ pass
217
+ await RabbitMQService.shutdown()
sycommon/synacos/feign.py CHANGED
@@ -46,7 +46,7 @@ from sycommon.synacos.nacos_service import NacosService
46
46
  # return None
47
47
 
48
48
 
49
- def feign_client(service_name: str, path_prefix: str = ""):
49
+ def feign_client(service_name: str, path_prefix: str = "", default_timeout: float | None = None):
50
50
  # Feign风格客户端装饰器
51
51
  """声明式HTTP客户端装饰器"""
52
52
  def decorator(cls):
@@ -56,6 +56,7 @@ def feign_client(service_name: str, path_prefix: str = ""):
56
56
  self.nacos_manager = NacosService(None)
57
57
  self.path_prefix = path_prefix
58
58
  self.session = aiohttp.ClientSession()
59
+ self.default_timeout = default_timeout
59
60
 
60
61
  def __getattr__(self, name):
61
62
  func = getattr(cls, name)
@@ -67,6 +68,8 @@ def feign_client(service_name: str, path_prefix: str = ""):
67
68
  path = request_meta.get("path", "")
68
69
  headers = request_meta.get("headers", {})
69
70
 
71
+ timeout = kwargs.pop('timeout', self.default_timeout)
72
+
70
73
  # 获取版本信息
71
74
  version = headers.get('s-y-version')
72
75
 
@@ -120,7 +123,7 @@ def feign_client(service_name: str, path_prefix: str = ""):
120
123
  headers=headers,
121
124
  params=params,
122
125
  data=data,
123
- timeout=10
126
+ timeout=timeout
124
127
  ) as response:
125
128
  return await self._handle_response(response)
126
129
  else:
@@ -131,7 +134,7 @@ def feign_client(service_name: str, path_prefix: str = ""):
131
134
  headers=headers,
132
135
  params=params,
133
136
  json=body,
134
- timeout=10
137
+ timeout=timeout
135
138
  ) as response:
136
139
  return await self._handle_response(response)
137
140
  except Exception as e:
@@ -188,15 +191,15 @@ def feign_upload(field_name: str = "file"):
188
191
 
189
192
 
190
193
  async def feign(service_name, api_path, method='GET', params=None, headers=None, file_path=None,
191
- path_params=None, request=None, body=None, files=None, form_data=None):
194
+ path_params=None, body=None, files=None, form_data=None, timeout=None):
192
195
  """
193
- feign 函数,支持 form-data 表单上传文件和其他字段
196
+ feign 函数,支持 form-data 表单上传文件和其他字段,支持自定义超时时间
194
197
 
195
198
  参数:
196
199
  files: 字典,用于上传文件,格式: {'field_name': (filename, file_content)}
197
200
  form_data: 字典,用于上传表单字段
201
+ timeout: 超时时间(秒),None 表示不设置超时
198
202
  """
199
- file_stream = None
200
203
  session = aiohttp.ClientSession()
201
204
  try:
202
205
  # 获取健康的服务实例
@@ -259,7 +262,7 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
259
262
  headers=headers,
260
263
  params=params,
261
264
  data=data,
262
- timeout=10
265
+ timeout=timeout
263
266
  ) as response:
264
267
  return await _handle_feign_response(response)
265
268
  else:
@@ -272,7 +275,7 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
272
275
  headers=headers,
273
276
  params=params,
274
277
  json=body,
275
- timeout=10
278
+ timeout=timeout
276
279
  ) as response:
277
280
  return await _handle_feign_response(response)
278
281
  except aiohttp.ClientError as e:
@@ -281,8 +284,9 @@ async def feign(service_name, api_path, method='GET', params=None, headers=None,
281
284
  print(f"请求服务接口时出错: {e}")
282
285
  return None
283
286
  except Exception as e:
284
- SYLogger.error(f"nacos:请求服务接口时出错: {e}")
285
- print(f"nacos:请求服务接口时出错: {e}")
287
+ import traceback
288
+ SYLogger.error(f"nacos:请求服务接口时出错: {traceback.format_exc()}")
289
+ print(f"nacos:请求服务接口时出错: {traceback.format_exc()}")
286
290
  return None
287
291
  finally:
288
292
  SYLogger.info(f"nacos:结束调用服务: {service_name}, {api_path}")