sycommon-python-lib 0.2.8a0__py3-none-any.whl → 0.2.8a1__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.
- sycommon/agent/deep_agent.py +7 -0
- sycommon/agent/multi_agent_team.py +4 -0
- sycommon/config/Config.py +24 -0
- sycommon/database/async_base_db_service.py +41 -6
- sycommon/database/async_database_service.py +84 -17
- sycommon/llm/sy_langfuse.py +1 -1
- sycommon/llm/token_usage_mysql_service.py +1 -1
- sycommon/logging/kafka_log.py +1 -1
- sycommon/models/base_http.py +52 -0
- sycommon/notice/uvicorn_monitor.py +4 -4
- sycommon/rabbitmq/rabbitmq_service_producer_manager.py +1 -2
- sycommon/sentry/sy_sentry.py +1 -1
- sycommon/tests/test_env_tenant_helper.py +72 -0
- {sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/METADATA +1 -1
- {sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/RECORD +18 -17
- {sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/WHEEL +1 -1
- {sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/top_level.txt +0 -0
sycommon/agent/deep_agent.py
CHANGED
|
@@ -160,6 +160,10 @@ class AgentConfig(BaseModel):
|
|
|
160
160
|
# (scoped 由服务端路由硬编码,user 由 MinIO 按 user 灌入)。
|
|
161
161
|
system_skills_allow: Optional[set[str]] = None
|
|
162
162
|
memory_dir: Optional[str] = None
|
|
163
|
+
# 额外 middleware(应用层注入,如技能使用上报、自定义拦截):
|
|
164
|
+
# 数字员工等项目自定义 AgentMiddleware 实例列表,create_deep_agent 会
|
|
165
|
+
# 追加到内置 middleware_list 之后执行。类型用 list 而非具体类避免循环依赖。
|
|
166
|
+
extra_middleware: list = []
|
|
163
167
|
|
|
164
168
|
@property
|
|
165
169
|
def skills_dir(self) -> Optional[str]:
|
|
@@ -1118,6 +1122,9 @@ async def create_deep_agent(
|
|
|
1118
1122
|
SkillWriteGuardMiddleware(), # 系统 /skills/system 只读守卫(先于白名单)
|
|
1119
1123
|
SandboxPathGuardMiddleware(user_id), # 宿主绝对路径规范化/拦截(防越权读他人沙箱文件)
|
|
1120
1124
|
]
|
|
1125
|
+
# 应用层注入的额外 middleware(如技能使用上报),排在内置之后
|
|
1126
|
+
if config.extra_middleware:
|
|
1127
|
+
middleware_list.extend(config.extra_middleware)
|
|
1121
1128
|
|
|
1122
1129
|
# RubricMiddleware:复用 FilesystemMiddleware 的工具(底层走 HTTPSandboxBackend HTTP 远程调用)
|
|
1123
1130
|
if config.rubric_max_iterations > 0 and sandbox_backend:
|
|
@@ -142,6 +142,8 @@ class TeamConfig(BaseModel):
|
|
|
142
142
|
|
|
143
143
|
sandbox_service_name: str = "shengye-platform-sandbox"
|
|
144
144
|
sandbox_timeout: int = 300
|
|
145
|
+
# 额外 middleware(应用层注入,如技能使用上报)
|
|
146
|
+
extra_middleware: list = []
|
|
145
147
|
# system 技能:本地仓库目录 → 同步到沙箱 /skills/system(只读)
|
|
146
148
|
system_skills_dir: Optional[str] = None
|
|
147
149
|
# user 技能:标记启用沙箱 /skills/user(可写,内容由 MinIO 灌入)
|
|
@@ -685,6 +687,8 @@ async def create_multi_agent_team(
|
|
|
685
687
|
SkillWriteGuardMiddleware(),
|
|
686
688
|
SandboxPathGuardMiddleware(user_id),
|
|
687
689
|
]
|
|
690
|
+
if config.extra_middleware:
|
|
691
|
+
middleware.extend(config.extra_middleware)
|
|
688
692
|
shared = config.shared_tools or [get_current_date]
|
|
689
693
|
|
|
690
694
|
def _build_subagent(member: TeamMember) -> CompiledSubAgent:
|
sycommon/config/Config.py
CHANGED
|
@@ -114,6 +114,30 @@ class Config(metaclass=SingletonMeta):
|
|
|
114
114
|
return es
|
|
115
115
|
raise ValueError(f"No configuration found for elasticsearch: {name}")
|
|
116
116
|
|
|
117
|
+
# 命名空间 → 默认租户 ID:prod/wsuat1 共享同一租户,其余环境另一租户
|
|
118
|
+
_PROD_NAMESPACES = ("prod", "wsuat1")
|
|
119
|
+
_DEFAULT_TENANT_PROD = "T000002"
|
|
120
|
+
_DEFAULT_TENANT_OTHER = "T000003"
|
|
121
|
+
|
|
122
|
+
def get_namespace_id(self, default=''):
|
|
123
|
+
"""获取当前环境标识(Nacos namespaceId,如 'prod'/'wsuat1'/'dev')。
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
default: 配置缺失时的默认值,按调用场景传入:
|
|
127
|
+
告警展示用 '未知环境',功能判断用 'dev'/'prod',缓存哨兵用 None。
|
|
128
|
+
"""
|
|
129
|
+
return self.config.get('Nacos', {}).get('namespaceId', default)
|
|
130
|
+
|
|
131
|
+
def get_tenant_id(self):
|
|
132
|
+
"""根据当前环境推导系统默认租户 ID。
|
|
133
|
+
|
|
134
|
+
prod / wsuat1 → "T000002",其余环境 → "T000003"。
|
|
135
|
+
仅作为系统内部消息(如 RabbitMQ SYSTEM 消息)的兜底默认值;
|
|
136
|
+
若调用方有请求上下文中的真实租户(x-tenant-header),应优先使用它。
|
|
137
|
+
"""
|
|
138
|
+
ns = self.get_namespace_id()
|
|
139
|
+
return self._DEFAULT_TENANT_PROD if ns in self._PROD_NAMESPACES else self._DEFAULT_TENANT_OTHER
|
|
140
|
+
|
|
117
141
|
def _process_config(self):
|
|
118
142
|
# 清空列表,防止 set_attr 多次调用时重复累积
|
|
119
143
|
self.llm_configs.clear()
|
|
@@ -29,12 +29,47 @@ def _is_retryable_tx_error(exc: Exception) -> bool:
|
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
class AsyncBaseDBService(metaclass=SingletonMeta):
|
|
32
|
-
"""数据库操作基础服务类,封装异步会话管理功能
|
|
32
|
+
"""数据库操作基础服务类,封装异步会话管理功能
|
|
33
|
+
|
|
34
|
+
engine 不在 __init__ 时绑定,而是每次 session 时按当前 event loop 动态
|
|
35
|
+
获取(AsyncDatabaseService.engine() 按 loop 隔离)。这样子类即使作为
|
|
36
|
+
模块级单例在 import 时实例化(此时 DB 未初始化、loop 未确定),也不会
|
|
37
|
+
把 engine 绑死在错误的时机/loop 上;后台线程在独立 loop 调用时同样能
|
|
38
|
+
拿到本 loop 的连接池。
|
|
39
|
+
"""
|
|
33
40
|
|
|
34
41
|
def __init__(self):
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
# 不缓存 engine/Session,延迟到每次使用时按当前 loop 创建,
|
|
43
|
+
# 见 _session_factory()。保留 engine 只读属性兼容旧子类(见下)。
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def engine(self):
|
|
48
|
+
"""当前 loop 的 engine(兼容旧子类直接访问 self.engine 的写法)
|
|
49
|
+
|
|
50
|
+
注意:仅在调试/日志场景使用;正式会话请走 session()/execute(),
|
|
51
|
+
不要把返回的 engine 跨 loop 缓存复用。
|
|
52
|
+
"""
|
|
53
|
+
return AsyncDatabaseService.engine()
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def Session(self) -> async_sessionmaker:
|
|
57
|
+
"""当前 loop 的会话工厂(兼容旧子类直接访问 self.Session 的写法)
|
|
58
|
+
|
|
59
|
+
用法与旧版一致:`async with self.Session() as session: ...`
|
|
60
|
+
但每次访问都按当前 loop 重新构建,不要将其返回值跨 loop 缓存。
|
|
61
|
+
"""
|
|
62
|
+
return self._session_factory()
|
|
63
|
+
|
|
64
|
+
def _session_factory(self) -> async_sessionmaker:
|
|
65
|
+
"""按当前 loop 创建会话工厂(engine 由 AsyncDatabaseService 按 loop 提供)"""
|
|
66
|
+
engine = AsyncDatabaseService.engine()
|
|
67
|
+
if engine is None:
|
|
68
|
+
raise RuntimeError(
|
|
69
|
+
"数据库未初始化:AsyncDatabaseService.engine() 返回 None,"
|
|
70
|
+
"请先通过 Services.plugins(database_service=...) 完成初始化")
|
|
71
|
+
return async_sessionmaker(
|
|
72
|
+
bind=engine,
|
|
38
73
|
class_=AsyncSession,
|
|
39
74
|
expire_on_commit=False
|
|
40
75
|
)
|
|
@@ -45,7 +80,7 @@ class AsyncBaseDBService(metaclass=SingletonMeta):
|
|
|
45
80
|
异步数据库会话上下文管理器
|
|
46
81
|
自动处理会话的创建、提交、回滚和关闭
|
|
47
82
|
"""
|
|
48
|
-
async with self.
|
|
83
|
+
async with self._session_factory()() as session:
|
|
49
84
|
try:
|
|
50
85
|
yield session
|
|
51
86
|
await session.commit()
|
|
@@ -81,7 +116,7 @@ class AsyncBaseDBService(metaclass=SingletonMeta):
|
|
|
81
116
|
last_exc = None
|
|
82
117
|
|
|
83
118
|
while True:
|
|
84
|
-
async with self.
|
|
119
|
+
async with self._session_factory()() as session:
|
|
85
120
|
try:
|
|
86
121
|
result = await fn(session)
|
|
87
122
|
await session.commit()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
2
2
|
from sqlalchemy import text, event
|
|
3
|
+
import asyncio
|
|
3
4
|
|
|
4
5
|
from sycommon.config.Config import SingletonMeta
|
|
5
6
|
from sycommon.config.DatabaseConfig import DatabaseConfig, convert_dict_keys
|
|
@@ -9,7 +10,19 @@ from sycommon.synacos.nacos_service import NacosService
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class AsyncDatabaseService(metaclass=SingletonMeta):
|
|
12
|
-
|
|
13
|
+
# 按 event loop 隔离的 engine 池:{loop: AsyncEngine}
|
|
14
|
+
#
|
|
15
|
+
# 为什么不是全局单 engine:aiomysql 连接的 socket stream 在首次读写时
|
|
16
|
+
# 绑定到当时的 event loop,跨 loop 复用会抛
|
|
17
|
+
# "got Future attached to a different loop"。
|
|
18
|
+
# 业务服务的后台线程(如定时刷盘)通常跑在独立 loop,必须各用各的连接。
|
|
19
|
+
#
|
|
20
|
+
# 兼容:engine() 保持无参,自动按"当前 running loop"返回对应 engine;
|
|
21
|
+
# 主 loop(setup_database 所在)返回初始化的那个,其他 loop 首次访问时
|
|
22
|
+
# 按同一份配置懒创建。单 loop 项目行为与之前完全一致。
|
|
23
|
+
_engines: dict = {}
|
|
24
|
+
_main_loop = None # setup_database 时的 loop
|
|
25
|
+
_db_config = None # DatabaseConfig,供其他 loop 懒建 engine
|
|
13
26
|
# setup 时记录入参,供 reload() 重建使用
|
|
14
27
|
_setup_args = None
|
|
15
28
|
|
|
@@ -24,8 +37,15 @@ class AsyncDatabaseService(metaclass=SingletonMeta):
|
|
|
24
37
|
# 初始化 DatabaseConnector (传入配置)
|
|
25
38
|
connector = AsyncDatabaseConnector(db_config)
|
|
26
39
|
|
|
27
|
-
#
|
|
28
|
-
|
|
40
|
+
# 记录主 loop 与其 engine
|
|
41
|
+
try:
|
|
42
|
+
AsyncDatabaseService._main_loop = asyncio.get_running_loop()
|
|
43
|
+
except RuntimeError:
|
|
44
|
+
AsyncDatabaseService._main_loop = None
|
|
45
|
+
AsyncDatabaseService._db_config = db_config
|
|
46
|
+
AsyncDatabaseService._engines = {
|
|
47
|
+
AsyncDatabaseService._main_loop: connector.engine
|
|
48
|
+
}
|
|
29
49
|
|
|
30
50
|
# 执行异步测试连接
|
|
31
51
|
if not await connector.test_connection():
|
|
@@ -39,43 +59,90 @@ class AsyncDatabaseService(metaclass=SingletonMeta):
|
|
|
39
59
|
|
|
40
60
|
@staticmethod
|
|
41
61
|
def engine():
|
|
42
|
-
|
|
62
|
+
"""返回当前 running loop 对应的 engine。
|
|
63
|
+
|
|
64
|
+
主 loop 返回初始化的共享 engine;其他 loop(后台线程)首次调用时
|
|
65
|
+
按同一份数据库配置懒创建独立 engine(连接归该 loop 专用)。
|
|
66
|
+
无 running loop(纯同步上下文)时回退主 engine。
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
loop = asyncio.get_running_loop()
|
|
70
|
+
except RuntimeError:
|
|
71
|
+
# 同步上下文(如 ORM 事件回调、脚本),回退主 engine
|
|
72
|
+
return AsyncDatabaseService._engines.get(
|
|
73
|
+
AsyncDatabaseService._main_loop)
|
|
74
|
+
|
|
75
|
+
if loop in AsyncDatabaseService._engines:
|
|
76
|
+
return AsyncDatabaseService._engines[loop]
|
|
77
|
+
|
|
78
|
+
# 其他 loop 首次访问:懒建专属 engine
|
|
79
|
+
if AsyncDatabaseService._db_config is None:
|
|
80
|
+
return None
|
|
81
|
+
engine = AsyncDatabaseService._build_engine()
|
|
82
|
+
AsyncDatabaseService._engines[loop] = engine
|
|
83
|
+
return engine
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _build_engine():
|
|
87
|
+
"""按当前 running loop 创建 engine(内部使用)"""
|
|
88
|
+
connector = AsyncDatabaseConnector(AsyncDatabaseService._db_config)
|
|
89
|
+
SYLogger.info(
|
|
90
|
+
f"AsyncDatabaseService 为非主 loop 创建独立 engine "
|
|
91
|
+
f"(共 {len(AsyncDatabaseService._engines) + 1} 个)")
|
|
92
|
+
return connector.engine
|
|
43
93
|
|
|
44
94
|
@staticmethod
|
|
45
95
|
async def dispose() -> None:
|
|
46
|
-
"""
|
|
47
|
-
|
|
96
|
+
"""释放所有 engine 的连接池资源(在各 loop 内安全地尽力释放)"""
|
|
97
|
+
engines = list(AsyncDatabaseService._engines.values())
|
|
98
|
+
AsyncDatabaseService._engines = {}
|
|
99
|
+
AsyncDatabaseService._main_loop = None
|
|
100
|
+
for engine in engines:
|
|
101
|
+
if engine is None:
|
|
102
|
+
continue
|
|
48
103
|
try:
|
|
49
|
-
|
|
104
|
+
# dispose 是协程但内部只做连接关闭,跨 loop 调用对
|
|
105
|
+
# 空闲连接安全;忙碌连接的 loop 此时应已停止
|
|
106
|
+
await engine.dispose()
|
|
50
107
|
except Exception as e:
|
|
51
108
|
SYLogger.error(f"AsyncDatabase engine dispose error: {e}")
|
|
52
|
-
finally:
|
|
53
|
-
AsyncDatabaseService._engine = None
|
|
54
109
|
|
|
55
110
|
@classmethod
|
|
56
111
|
async def reload(cls) -> None:
|
|
57
112
|
"""配置热更新:dispose 旧引擎并按最新配置重建,失败回退。"""
|
|
58
113
|
if not cls._setup_args:
|
|
59
114
|
return
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
from sycommon.config.Config import SingletonMeta
|
|
63
|
-
SingletonMeta._instances.pop(AsyncDatabaseConnector, None)
|
|
115
|
+
old_engines = dict(cls._engines)
|
|
116
|
+
old_main_loop = cls._main_loop
|
|
64
117
|
try:
|
|
65
118
|
await cls.setup_database(*cls._setup_args)
|
|
66
119
|
except Exception as e:
|
|
67
120
|
SYLogger.error(f"AsyncDatabaseService reload 重建失败: {e}", exc_info=True)
|
|
68
|
-
cls.
|
|
121
|
+
cls._engines = old_engines
|
|
122
|
+
cls._main_loop = old_main_loop
|
|
69
123
|
return
|
|
70
|
-
# 新 engine
|
|
71
|
-
|
|
124
|
+
# 新 engine 建好,释放旧的(含其他 loop 的,其连接已失效,
|
|
125
|
+
# 下次访问会按新 _db_config 自动重建)
|
|
126
|
+
for loop, old_engine in old_engines.items():
|
|
127
|
+
if old_engine is None:
|
|
128
|
+
continue
|
|
72
129
|
try:
|
|
73
130
|
await old_engine.dispose()
|
|
74
131
|
except Exception as e:
|
|
75
132
|
SYLogger.error(f"AsyncDatabaseService reload 释放旧 engine 失败: {e}")
|
|
76
133
|
|
|
77
134
|
|
|
78
|
-
class AsyncDatabaseConnector
|
|
135
|
+
class AsyncDatabaseConnector:
|
|
136
|
+
"""数据库连接器(每次实例化都新建 engine)
|
|
137
|
+
|
|
138
|
+
不能用 SingletonMeta:AsyncDatabaseService 按 event loop 隔离 engine,
|
|
139
|
+
需要为每个 loop 各建一个连接池;单例会让所有 loop 拿到同一个 engine,
|
|
140
|
+
跨 loop 使用必抛 "attached to a different loop"。
|
|
141
|
+
|
|
142
|
+
注意:reload() 原本靠 `SingletonMeta._instances.pop(AsyncDatabaseConnector)`
|
|
143
|
+
清缓存强制重建——去掉单例后每次实例化本身就是全新 engine,无需再清。
|
|
144
|
+
"""
|
|
145
|
+
|
|
79
146
|
def __init__(self, db_config: DatabaseConfig):
|
|
80
147
|
# 从 DatabaseConfig 中提取数据库连接信息
|
|
81
148
|
self.db_user = db_config.username
|
sycommon/llm/sy_langfuse.py
CHANGED
|
@@ -48,7 +48,7 @@ class LangfuseInitializer(metaclass=SingletonMeta):
|
|
|
48
48
|
|
|
49
49
|
server_name = config_dict.get('Name', '')
|
|
50
50
|
langfuse_configs = config_dict.get('LangfuseConfig', [])
|
|
51
|
-
environment =
|
|
51
|
+
environment = Config().get_namespace_id('')
|
|
52
52
|
|
|
53
53
|
# 3. 查找匹配的配置项
|
|
54
54
|
target_config = next(
|
|
@@ -134,7 +134,7 @@ class TokenUsageMySQLService(metaclass=SingletonMeta):
|
|
|
134
134
|
f"TokenUsageMySQLService 加载 service_name: {cls._service_name}")
|
|
135
135
|
|
|
136
136
|
if cls._system_env is None:
|
|
137
|
-
env =
|
|
137
|
+
env = Config().get_namespace_id(None)
|
|
138
138
|
if env:
|
|
139
139
|
cls._system_env = env
|
|
140
140
|
logging.info(
|
sycommon/logging/kafka_log.py
CHANGED
|
@@ -236,7 +236,7 @@ class KafkaSink:
|
|
|
236
236
|
"tenantId": "",
|
|
237
237
|
"userId": "",
|
|
238
238
|
"customerId": "",
|
|
239
|
-
"env": Config().
|
|
239
|
+
"env": Config().get_namespace_id(''),
|
|
240
240
|
"priReqSource": "",
|
|
241
241
|
"reqSource": "",
|
|
242
242
|
"serviceId": self.service_id,
|
sycommon/models/base_http.py
CHANGED
|
@@ -99,3 +99,55 @@ def error_content(
|
|
|
99
99
|
) -> dict:
|
|
100
100
|
"""构建错误响应内容字典,支持自定义错误码"""
|
|
101
101
|
return build_response_content(data=data, code=code, message=message)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# =====================================================================
|
|
105
|
+
# HTTP 语义响应(新方式,推荐使用)
|
|
106
|
+
#
|
|
107
|
+
# 与上面 0/1 业务码的区别:code 直接用 HTTP 状态码语义——
|
|
108
|
+
# 成功 code=200 / 失败 code=500,HTTP status 同步对应(200 / 500),
|
|
109
|
+
# success 由 code 是否为 2xx 推导。不再用 0/1 代表成功失败。
|
|
110
|
+
# 旧函数保留兼容,新项目请用 ok() / fail()。
|
|
111
|
+
# =====================================================================
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def http_response(
|
|
115
|
+
data: T | Any = None,
|
|
116
|
+
code: int = status.HTTP_200_OK,
|
|
117
|
+
message: str = None,
|
|
118
|
+
) -> JSONResponse:
|
|
119
|
+
"""按 HTTP 状态码语义构建响应,code 即业务码也是 HTTP status。
|
|
120
|
+
|
|
121
|
+
success 自动按 code 是否为 2xx 推导。
|
|
122
|
+
"""
|
|
123
|
+
content = BaseResponseModel(
|
|
124
|
+
code=code,
|
|
125
|
+
message=message,
|
|
126
|
+
success=200 <= code < 300,
|
|
127
|
+
data=data,
|
|
128
|
+
)
|
|
129
|
+
if isinstance(content.data, BaseModel):
|
|
130
|
+
body = {
|
|
131
|
+
"code": content.code,
|
|
132
|
+
"message": content.message,
|
|
133
|
+
"success": content.success,
|
|
134
|
+
"data": content.data.model_dump(),
|
|
135
|
+
}
|
|
136
|
+
else:
|
|
137
|
+
body = content.model_dump()
|
|
138
|
+
return JSONResponse(content=body, status_code=code)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def ok(data: T | Any = None, message: str = None) -> JSONResponse:
|
|
142
|
+
"""成功响应:code=200, success=true, HTTP 200"""
|
|
143
|
+
return http_response(data=data, code=status.HTTP_200_OK, message=message)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def fail(
|
|
147
|
+
message: str = None,
|
|
148
|
+
data: T | Any = None,
|
|
149
|
+
code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
150
|
+
) -> JSONResponse:
|
|
151
|
+
"""失败响应:默认 code=500, success=false, HTTP 500(可用 code 覆盖)"""
|
|
152
|
+
return http_response(data=data, code=code, message=message)
|
|
153
|
+
|
|
@@ -94,7 +94,7 @@ async def send_webhook(error_info: dict = None, webhook: str = None):
|
|
|
94
94
|
try:
|
|
95
95
|
config = Config().config
|
|
96
96
|
service_name = config.get('Name', "未知服务")
|
|
97
|
-
env =
|
|
97
|
+
env = Config().get_namespace_id('未知环境')
|
|
98
98
|
webHook = get_webhook()
|
|
99
99
|
except Exception as e:
|
|
100
100
|
service_name = "未知服务"
|
|
@@ -138,7 +138,7 @@ def run(*args, webhook: str = None, **kwargs):
|
|
|
138
138
|
"""
|
|
139
139
|
# 判断环境
|
|
140
140
|
try:
|
|
141
|
-
env = Config().
|
|
141
|
+
env = Config().get_namespace_id('dev')
|
|
142
142
|
except:
|
|
143
143
|
env = 'dev'
|
|
144
144
|
|
|
@@ -249,7 +249,7 @@ async def send_mq_disconnect_alert(
|
|
|
249
249
|
try:
|
|
250
250
|
config = Config().config
|
|
251
251
|
service_name = config.get('Name', "未知服务")
|
|
252
|
-
env =
|
|
252
|
+
env = Config().get_namespace_id('未知环境')
|
|
253
253
|
except Exception:
|
|
254
254
|
service_name = "未知服务"
|
|
255
255
|
env = "未知环境"
|
|
@@ -309,7 +309,7 @@ async def send_nacos_disconnect_alert(
|
|
|
309
309
|
try:
|
|
310
310
|
config = Config().config
|
|
311
311
|
display_service_name = config.get('Name', "未知服务")
|
|
312
|
-
env =
|
|
312
|
+
env = Config().get_namespace_id('未知环境')
|
|
313
313
|
except Exception:
|
|
314
314
|
display_service_name = "未知服务"
|
|
315
315
|
env = "未知环境"
|
|
@@ -198,8 +198,7 @@ class RabbitMQProducerManager(RabbitMQClientManager):
|
|
|
198
198
|
)
|
|
199
199
|
|
|
200
200
|
# 构建消息头
|
|
201
|
-
|
|
202
|
-
tenant_id = "T000002" if namespace_id == "prod" or namespace_id == "wsuat1" else "T000003"
|
|
201
|
+
tenant_id = Config().get_tenant_id()
|
|
203
202
|
mq_header = {
|
|
204
203
|
"context": SsoUser(
|
|
205
204
|
tenant_id=tenant_id,
|
sycommon/sentry/sy_sentry.py
CHANGED
|
@@ -9,7 +9,7 @@ from sentry_sdk.integrations.fastapi import FastApiIntegration
|
|
|
9
9
|
def sy_sentry_init():
|
|
10
10
|
config = Config().config
|
|
11
11
|
server_name = config.get('Name', '')
|
|
12
|
-
environment =
|
|
12
|
+
environment = Config().get_namespace_id('')
|
|
13
13
|
sentry_configs = config.get('SentryConfig', [])
|
|
14
14
|
target_config = next(
|
|
15
15
|
(item for item in sentry_configs if item.get('name') == server_name), None)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Config.get_namespace_id / get_tenant_id 统一环境/租户解析 单测。
|
|
2
|
+
|
|
3
|
+
锁定:
|
|
4
|
+
- namespaceId 原样返回;配置缺失时按调用方传入的 default 返回。
|
|
5
|
+
- 租户映射: prod / wsuat1 → T000002; dev / '' / None / 其它 → T000003。
|
|
6
|
+
|
|
7
|
+
通过 monkeypatch Config 单例的 config 覆盖分支,无需真实 app.yaml。
|
|
8
|
+
"""
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from sycommon.config.Config import Config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _set_config(monkeypatch, nacos_block):
|
|
15
|
+
"""把 Config 单例替换为只含 Nacos 段的实例。
|
|
16
|
+
|
|
17
|
+
跳过 __init__(它要求 app.yaml 存在),直接构造实例并注入单例缓存,
|
|
18
|
+
使 Config() 返回该实例。
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
nacos_block: Nacos 段内容。传 None 表示整个 Nacos key 都没有,
|
|
22
|
+
传 {} 表示有 key 但无 namespaceId,
|
|
23
|
+
传 {"namespaceId": "prod"} 表示正常配置。
|
|
24
|
+
"""
|
|
25
|
+
cfg = {} if nacos_block is None else {"Nacos": nacos_block}
|
|
26
|
+
inst = Config.__new__(Config)
|
|
27
|
+
inst.config = cfg
|
|
28
|
+
monkeypatch.setitem(Config._instances, Config, inst)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"nacos_block, expected",
|
|
33
|
+
[
|
|
34
|
+
({"namespaceId": "prod"}, "prod"),
|
|
35
|
+
({"namespaceId": "wsuat1"}, "wsuat1"),
|
|
36
|
+
({"namespaceId": "dev"}, "dev"),
|
|
37
|
+
({}, ""),
|
|
38
|
+
(None, ""),
|
|
39
|
+
],
|
|
40
|
+
)
|
|
41
|
+
def test_get_namespace_id_returns_configured_value(monkeypatch, nacos_block, expected):
|
|
42
|
+
_set_config(monkeypatch, nacos_block)
|
|
43
|
+
assert Config().get_namespace_id() == expected
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize("default", ["未知环境", "dev", None])
|
|
47
|
+
def test_get_namespace_id_returns_default_when_missing(monkeypatch, default):
|
|
48
|
+
# Nacos 段缺失 —— 命中 default
|
|
49
|
+
_set_config(monkeypatch, None)
|
|
50
|
+
assert Config().get_namespace_id(default) == default
|
|
51
|
+
# Nacos 段在但无 namespaceId —— 同样命中 default
|
|
52
|
+
_set_config(monkeypatch, {})
|
|
53
|
+
assert Config().get_namespace_id(default) == default
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@pytest.mark.parametrize("ns", ["prod", "wsuat1"])
|
|
57
|
+
def test_get_tenant_id_prod_namespaces(monkeypatch, ns):
|
|
58
|
+
_set_config(monkeypatch, {"namespaceId": ns})
|
|
59
|
+
assert Config().get_tenant_id() == Config._DEFAULT_TENANT_PROD == "T000002"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize("nacos_block", [{"namespaceId": "dev"}, {}, None])
|
|
63
|
+
def test_get_tenant_id_other_namespaces(monkeypatch, nacos_block):
|
|
64
|
+
_set_config(monkeypatch, nacos_block)
|
|
65
|
+
assert Config().get_tenant_id() == Config._DEFAULT_TENANT_OTHER == "T000003"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_tenant_mapping_constants_locked():
|
|
69
|
+
"""锁定映射表,防止后续误改租户 ID 或判定环境集合。"""
|
|
70
|
+
assert Config._PROD_NAMESPACES == ("prod", "wsuat1")
|
|
71
|
+
assert Config._DEFAULT_TENANT_PROD == "T000002"
|
|
72
|
+
assert Config._DEFAULT_TENANT_OTHER == "T000003"
|
|
@@ -136,8 +136,8 @@ sycommon/services.py,sha256=iC73h5d5bpNqW2qjRUGODZFhUhcwvKVnKUFLKJlBSq0,25322
|
|
|
136
136
|
sycommon/agent/__init__.py,sha256=mxceAeUifQ-DKvWp7ZEJIFlmOCb5wpYHPGQw3rwEN8I,4378
|
|
137
137
|
sycommon/agent/agent_manager.py,sha256=UhhaekEumT7g4v_Z1UB4jTp13X0n8M8erYaQdkGGWkA,13620
|
|
138
138
|
sycommon/agent/chat_events.py,sha256=t7qWa6OrIWLfqtd1AnqaVP67QWkn1JxpCBTZYQpoLuM,14226
|
|
139
|
-
sycommon/agent/deep_agent.py,sha256=
|
|
140
|
-
sycommon/agent/multi_agent_team.py,sha256=
|
|
139
|
+
sycommon/agent/deep_agent.py,sha256=_pWnZ_qUsQrE3GE6Ck1eYW3WmPtHSxZ39hzdOf6Y7FA,77759
|
|
140
|
+
sycommon/agent/multi_agent_team.py,sha256=imovcL-yqfXnT6jsTcaDZJbUHW8vUWyKAGELV47SntM,33803
|
|
141
141
|
sycommon/agent/reasoning_content_patch.py,sha256=HZ_If0qVDHlz_b1mDT3tuTqbExhzRVhP4VgsoDt0JLc,3945
|
|
142
142
|
sycommon/agent/resume_compactor.py,sha256=5ZdjKWSAH8P3eyVlmu_MBos4o-2aYDY5I46kf_01eHc,9049
|
|
143
143
|
sycommon/agent/summarization_utils.py,sha256=Yiu_zYn6cXUOq2NOsUh7tljqvmcOw6WAUKREONP2pLM,18923
|
|
@@ -171,7 +171,7 @@ sycommon/auth/oa_crypto.py,sha256=xpY1R1Bj3KLENXB0TuThB6Eku1E9PYjcoSpOdgDmCgc,18
|
|
|
171
171
|
sycommon/auth/oa_service.py,sha256=kLepV9zgqpZoaB73DRPpMA5tJJQjoaDtQPdzBcGXeak,6235
|
|
172
172
|
sycommon/auth/wecom_ldap_service.py,sha256=7yw4gvoaaT2G1SuL_jC6kTORgMuT4GVYbusFbPuOb6g,43173
|
|
173
173
|
sycommon/config/ACPAgentConfig.py,sha256=paAa0c0gy1RnDDum-Q6m7QLTe7lhz2dsFt4r72QtmWE,3202
|
|
174
|
-
sycommon/config/Config.py,sha256=
|
|
174
|
+
sycommon/config/Config.py,sha256=m58rd3WIyb7fCexR7pelFUcmnnoL0wa9DVwIXtAbf_c,9068
|
|
175
175
|
sycommon/config/DatabaseConfig.py,sha256=ILiUuYT9_xJZE2W-RYuC3JCt_YLKc1sbH13-MHIOPhg,804
|
|
176
176
|
sycommon/config/ElasticsearchConfig.py,sha256=fO9ZPMgJxSg1-UyDJ90wO6UvYy-jscwPJsSkXgx9qTU,2308
|
|
177
177
|
sycommon/config/EmbeddingConfig.py,sha256=gPKwiDYbeu1GpdIZXMmgqM7JqBIzCXi0yYuGRLZooMI,362
|
|
@@ -186,8 +186,8 @@ sycommon/config/SkillApiWhitelistConfig.py,sha256=-fimbAofnYSZRhksmvg_rrZOJPc76w
|
|
|
186
186
|
sycommon/config/XxlJobConfig.py,sha256=VSG6dn9ysfUVunOs7PqugyZUGJWmX_cEePz2ZCfqHtU,392
|
|
187
187
|
sycommon/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
188
188
|
sycommon/config/config_change_notifier.py,sha256=7qQe7vdBS1zohEfVq0d5IaDqYsLdgd2ckF4mKKgh0gE,6848
|
|
189
|
-
sycommon/database/async_base_db_service.py,sha256=
|
|
190
|
-
sycommon/database/async_database_service.py,sha256=
|
|
189
|
+
sycommon/database/async_base_db_service.py,sha256=8mPZ2yI1hIYvuHYxcpeU0gus_1WGjrpRqv2bm9TIBCk,6186
|
|
190
|
+
sycommon/database/async_database_service.py,sha256=fLc0dyLCA51kbjFH4dE4oab44fLnn8Ec26EH7TsY-So,9888
|
|
191
191
|
sycommon/database/base_db_service.py,sha256=urdAibaCUt3hxwwKzEMBceWkIwsw3ja_WDJ1QKc8nLU,4116
|
|
192
192
|
sycommon/database/database_service.py,sha256=svkg6MBxcar6nWSepBv40mSfpZi5tVecloTDu1O1Tns,6099
|
|
193
193
|
sycommon/database/elasticsearch_service.py,sha256=BJnBcoeSDtK1a1IcCyLbzQaOMb5hCfIh0wgBRQ9XmpU,4257
|
|
@@ -212,16 +212,16 @@ sycommon/llm/llm_with_token_tracking.py,sha256=VJYHQSSuqvAbJ7qFs7Zml_thFDZoqL2rX
|
|
|
212
212
|
sycommon/llm/native_with_fallback_runnable.py,sha256=8g2q8cRqJv2miY7GrZz_f-zNmHxdb_5WSujD3frqObQ,8035
|
|
213
213
|
sycommon/llm/output_fixing_runnable.py,sha256=wGHBKRpUjDz4ONJulTvG5UzJxZr1iK3ULkosaKqjF0w,8113
|
|
214
214
|
sycommon/llm/struct_token.py,sha256=WYNa7o2cC_3X6dJw3ZN1Zgn83HmhdD8GCUlSiEV7qus,22353
|
|
215
|
-
sycommon/llm/sy_langfuse.py,sha256=
|
|
215
|
+
sycommon/llm/sy_langfuse.py,sha256=Y0XzFOeoJDOCft9fPqqCVaXugM39SImdXga937nVhmo,6095
|
|
216
216
|
sycommon/llm/token_usage_es_service.py,sha256=-RBCwZ8jS436n8DBUBm3Lf5O6TsQMaQgIj5oYBLO4Ss,19909
|
|
217
|
-
sycommon/llm/token_usage_mysql_service.py,sha256=
|
|
217
|
+
sycommon/llm/token_usage_mysql_service.py,sha256=PfGaQQRY7bl7UZbxnMOjtx4sN-8V2e9kN2PU0H8GIao,32627
|
|
218
218
|
sycommon/llm/token_utils.py,sha256=C_9_rhsOx8AhY16p00R9aMYnGEhtbR_2acXm1u5ZeTE,6647
|
|
219
219
|
sycommon/llm/usage_token.py,sha256=uFskOhl6IMeJtysJ6tMAmBb2taVLm5lRWjqcrbnkx54,8736
|
|
220
220
|
sycommon/llm/tiktoken_cache/9b5ad71b2ce5302211f9c61530b329a4922fc6a4,sha256=Ijkht27pm96ZW3_3OFE-7xAPtR0YyTWXoRO8_-hlsqc,1681126
|
|
221
221
|
sycommon/llm/tiktoken_cache/fb374d419588a4632f3f557e76b4b70aebbca790,sha256=RGqVOMtsNI41FhINfAiwn1fDZJXirP_-WaW_iwz7Gi0,3613922
|
|
222
222
|
sycommon/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
223
223
|
sycommon/logging/async_sql_logger.py,sha256=TIUAqK8_F9H7Graw00hNjEI3DJ1ZRo5fRUcnqaMZlbU,2187
|
|
224
|
-
sycommon/logging/kafka_log.py,sha256=
|
|
224
|
+
sycommon/logging/kafka_log.py,sha256=M-VxIilYw_89gtIfn3xlavQT6kwnlEsBGxK8ylS_UXw,31024
|
|
225
225
|
sycommon/logging/log_masker.py,sha256=GYf4NA1YXF070r0MUkj2oh4bygaZo0hEnDih734a-eE,15923
|
|
226
226
|
sycommon/logging/logger_levels.py,sha256=xJBzPV2H7GpEQggOIjyckEsA4yf6s4NpI9ckgO3xWKA,1154
|
|
227
227
|
sycommon/logging/logger_wrapper.py,sha256=TtzmGw_K5NjBGBq9Gjqtnh834izAv8TQwsOeL2fIbAI,481
|
|
@@ -243,7 +243,7 @@ sycommon/middleware/token_tracking.py,sha256=rEbgV1bgWMdzAERx4aq5XAvOIT6jTY_tK1P
|
|
|
243
243
|
sycommon/middleware/tool_result_truncation.py,sha256=dTh2VHyNVGvl7CyjsZ4SqAxaabR5hnL-oB0bf2-S9Pk,12534
|
|
244
244
|
sycommon/middleware/traceid.py,sha256=U124PDNS4xhPVXcfpfTJn7dTEMsE0V4UT5umrxmjD4Y,16891
|
|
245
245
|
sycommon/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
246
|
-
sycommon/models/base_http.py,sha256=
|
|
246
|
+
sycommon/models/base_http.py,sha256=efxtsxmQmInlsRVqQU9P17MSVXx3TsNmgimMlz0hNV0,4788
|
|
247
247
|
sycommon/models/log.py,sha256=rZpj6VkDRxK3B6H7XSeWdYZshU8F0Sks8bq1p6pPlDw,500
|
|
248
248
|
sycommon/models/mcp_server_config.py,sha256=cm_oDEDXVJR4nCKgWPli5zFI0DUy6xtvRs-___EWAW8,1768
|
|
249
249
|
sycommon/models/mqlistener_config.py,sha256=ZFGg_vc9HcIV22L5Jgrynkjj5B6Ci8ETQNUbNUiXrVw,1830
|
|
@@ -255,7 +255,7 @@ sycommon/models/token_usage.py,sha256=w3Aash7jUVSUIgKxBUzI6SqJ9VV5Pk7rLBbMUlLv6n
|
|
|
255
255
|
sycommon/models/token_usage_mysql.py,sha256=eWnCvI2FDwyTA_fr4wWQtAmUnFJgLwrt0WXMXHLAESQ,2633
|
|
256
256
|
sycommon/models/xxljob_handler_config.py,sha256=XiSv9AlJ8UsLRai4hvTMeiSZ6VsO5Ijx8HcDwLxJL64,376
|
|
257
257
|
sycommon/notice/__init__.py,sha256=cVYbKlLLMFHckyDpx21CTZCMTufqZk_uHuEIxUNxwx4,104
|
|
258
|
-
sycommon/notice/uvicorn_monitor.py,sha256=
|
|
258
|
+
sycommon/notice/uvicorn_monitor.py,sha256=_SlLB91N7g-Jt_xP_AX1IWFgA7_1lCqsQFwm_Y6QjFk,11756
|
|
259
259
|
sycommon/notice/wecom_message.py,sha256=_oA6hg0Ask7NkFbvUpL78WOmyUb6uW7KcY71GhNKiJY,12363
|
|
260
260
|
sycommon/rabbitmq/process_pool_consumer.py,sha256=EXdNypgathjTfPqkccoAW0HiVB9HuZLGhPbWUcYh0_Q,29750
|
|
261
261
|
sycommon/rabbitmq/rabbitmq_client.py,sha256=TmIOtfb_Wx-swX2s93GgAjR7mzs9KeXy5kA_8UgE1vI,28168
|
|
@@ -265,9 +265,9 @@ sycommon/rabbitmq/rabbitmq_service_client_manager.py,sha256=UZgpD6n0mdo5u3gaGW-j
|
|
|
265
265
|
sycommon/rabbitmq/rabbitmq_service_connection_monitor.py,sha256=jPr9OWAq_nRGWAt7-q3qdOdhtT3EC6MShhNu9fiEpqk,8701
|
|
266
266
|
sycommon/rabbitmq/rabbitmq_service_consumer_manager.py,sha256=GeiS1GEtny5MxopIdKOCfeRB1AzkHZKPXKq9_jwg1KI,10274
|
|
267
267
|
sycommon/rabbitmq/rabbitmq_service_core.py,sha256=WeYtMAxGXpCJoVIVMRNrWSBTMnTDQCLVXiOn03iGAjg,7488
|
|
268
|
-
sycommon/rabbitmq/rabbitmq_service_producer_manager.py,sha256=
|
|
268
|
+
sycommon/rabbitmq/rabbitmq_service_producer_manager.py,sha256=cersEoHvl4__YqORYnqTckRl2ZlKV1j_zGamPKqsp-A,9895
|
|
269
269
|
sycommon/sentry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
270
|
-
sycommon/sentry/sy_sentry.py,sha256=
|
|
270
|
+
sycommon/sentry/sy_sentry.py,sha256=Os-BISmJvjaSarHxqWH_8u7KBk-k2XMVZfoj5UUzqso,1454
|
|
271
271
|
sycommon/sse/__init__.py,sha256=u-F-bzxb6g57JhkUwWxrnQYNSmZS30BYA0Tkrq2EKVg,194
|
|
272
272
|
sycommon/sse/event.py,sha256=k_rBJy23R7crtzQeetT0Q73D8o5-5p-eESGSs_BPOj0,2797
|
|
273
273
|
sycommon/sse/sse.py,sha256=OQ3ElV8WCi-AD3-e0nbiUF28Syf6GRpGztneWTn77EM,10356
|
|
@@ -294,6 +294,7 @@ sycommon/tests/test_deep_agent.py,sha256=34gP2KL8SSmtqqhaA9OV97OAl_I0T4TfEutZrR_
|
|
|
294
294
|
sycommon/tests/test_dynamic_max_tokens.py,sha256=mk2QXZIxpyClEBvS9g88NXr9xmXoNdVrIjFAsN5t6CQ,17934
|
|
295
295
|
sycommon/tests/test_email.py,sha256=-oPtYVGQzJ3Cv-op3ZNRMeyYOF-UNidGJC35CHRgjGQ,5442
|
|
296
296
|
sycommon/tests/test_encoding_cache.py,sha256=EqeN2x9RuyS5Xg8DhAfnzI-ezzaIaitSCZtXnfvALgA,4794
|
|
297
|
+
sycommon/tests/test_env_tenant_helper.py,sha256=WCaajpVmCPPpV_Y2rvD5bTVG_ZDiEHnb4G8gGGSe0JA,2694
|
|
297
298
|
sycommon/tests/test_exp_concurrent_migrate.py,sha256=2FY60udoClkrZPyDjTp-26KvMUbiKiWwT2502tb3YjE,5678
|
|
298
299
|
sycommon/tests/test_exp_delete_queue_with_consumers.py,sha256=L9RWLOzUtTEEpzad-VGkOkPCbCVmR3Znl6WCezxfSMY,5695
|
|
299
300
|
sycommon/tests/test_fault_injection_real.py,sha256=vqTmQXeay5VXq2DqXUKs1KdN-EbpWj9zoxhgpeCEHms,18986
|
|
@@ -326,8 +327,8 @@ sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
|
|
|
326
327
|
sycommon/tools/user_id.py,sha256=o-zsubGSR5rSSZf8Pk8B7AhE21ahDTTIW5JZmJO6Flw,4761
|
|
327
328
|
sycommon/xxljob/__init__.py,sha256=7eoBlQxv-B39IfRSCY2bkqdGYs1QRe1umAWd88VMEEM,86
|
|
328
329
|
sycommon/xxljob/xxljob_service.py,sha256=1yifwIBNGsCIxLnQjHKiBlbsigc_zvPH-dMTZcNxe-Q,7649
|
|
329
|
-
sycommon_python_lib-0.2.
|
|
330
|
-
sycommon_python_lib-0.2.
|
|
331
|
-
sycommon_python_lib-0.2.
|
|
332
|
-
sycommon_python_lib-0.2.
|
|
333
|
-
sycommon_python_lib-0.2.
|
|
330
|
+
sycommon_python_lib-0.2.8a1.dist-info/METADATA,sha256=lhsnYQpDEPhhOBsWNk16ltiUVesU8sIrj1n2SvgSMWw,7996
|
|
331
|
+
sycommon_python_lib-0.2.8a1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
332
|
+
sycommon_python_lib-0.2.8a1.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
|
|
333
|
+
sycommon_python_lib-0.2.8a1.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
|
|
334
|
+
sycommon_python_lib-0.2.8a1.dist-info/RECORD,,
|
{sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.8a0.dist-info → sycommon_python_lib-0.2.8a1.dist-info}/top_level.txt
RENAMED
|
File without changes
|