ErisPulse 2.4.6.dev6__tar.gz → 2.4.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/PKG-INFO +1 -1
  2. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/pyproject.toml +1 -1
  3. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/client.py +95 -44
  4. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/client.pyi +1 -1
  5. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/.gitignore +0 -0
  6. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/LICENSE +0 -0
  7. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/README.md +0 -0
  8. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/__init__.py +0 -0
  9. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/__init__.pyi +0 -0
  10. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/base.py +0 -0
  11. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/base.pyi +0 -0
  12. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/cli.py +0 -0
  13. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/cli.pyi +0 -0
  14. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/__init__.py +0 -0
  15. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/__init__.pyi +0 -0
  16. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/create.py +0 -0
  17. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/create.pyi +0 -0
  18. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/init.py +0 -0
  19. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/init.pyi +0 -0
  20. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/install.py +0 -0
  21. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/install.pyi +0 -0
  22. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/list.py +0 -0
  23. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/list.pyi +0 -0
  24. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/list_remote.py +0 -0
  25. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/list_remote.pyi +0 -0
  26. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/run.py +0 -0
  27. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/run.pyi +0 -0
  28. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/self_update.py +0 -0
  29. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/self_update.pyi +0 -0
  30. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/uninstall.py +0 -0
  31. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/uninstall.pyi +0 -0
  32. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/upgrade.py +0 -0
  33. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/commands/upgrade.pyi +0 -0
  34. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/console.py +0 -0
  35. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/console.pyi +0 -0
  36. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/registry.py +0 -0
  37. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/registry.pyi +0 -0
  38. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/utils/__init__.py +0 -0
  39. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/utils/__init__.pyi +0 -0
  40. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/utils/display.py +0 -0
  41. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/utils/display.pyi +0 -0
  42. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/utils/package_manager.py +0 -0
  43. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/CLI/utils/package_manager.pyi +0 -0
  44. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/__init__.py +0 -0
  45. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/__init__.pyi +0 -0
  46. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/adapter.py +0 -0
  47. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/adapter.pyi +0 -0
  48. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/client.py +0 -0
  49. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/client.pyi +0 -0
  50. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/errors.py +0 -0
  51. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/errors.pyi +0 -0
  52. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/manager.py +0 -0
  53. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/manager.pyi +0 -0
  54. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/module.py +0 -0
  55. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/module.pyi +0 -0
  56. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/router.py +0 -0
  57. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/router.pyi +0 -0
  58. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/storage.py +0 -0
  59. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/storage.pyi +0 -0
  60. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/websocket.py +0 -0
  61. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Bases/websocket.pyi +0 -0
  62. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/__init__.py +0 -0
  63. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/__init__.pyi +0 -0
  64. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/base.py +0 -0
  65. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/base.pyi +0 -0
  66. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/command.py +0 -0
  67. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/command.pyi +0 -0
  68. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/message.py +0 -0
  69. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/message.pyi +0 -0
  70. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/message_builder.py +0 -0
  71. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/message_builder.pyi +0 -0
  72. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/meta.py +0 -0
  73. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/meta.pyi +0 -0
  74. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/notice.py +0 -0
  75. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/notice.pyi +0 -0
  76. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/request.py +0 -0
  77. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/request.pyi +0 -0
  78. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/session_type.py +0 -0
  79. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/session_type.pyi +0 -0
  80. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/wrapper.py +0 -0
  81. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/Event/wrapper.pyi +0 -0
  82. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/__init__.py +0 -0
  83. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/__init__.pyi +0 -0
  84. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/adapter.py +0 -0
  85. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/adapter.pyi +0 -0
  86. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/config.py +0 -0
  87. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/config.pyi +0 -0
  88. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/constants.py +0 -0
  89. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/constants.pyi +0 -0
  90. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/lifecycle.py +0 -0
  91. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/lifecycle.pyi +0 -0
  92. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/logger.py +0 -0
  93. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/logger.pyi +0 -0
  94. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/module.py +0 -0
  95. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/module.pyi +0 -0
  96. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/router.py +0 -0
  97. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/router.pyi +0 -0
  98. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/storage.py +0 -0
  99. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/Core/storage.pyi +0 -0
  100. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/__init__.py +0 -0
  101. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/__init__.pyi +0 -0
  102. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/__main__.py +0 -0
  103. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/__main__.pyi +0 -0
  104. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/__init__.py +0 -0
  105. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/__init__.pyi +0 -0
  106. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/adapter.py +0 -0
  107. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/adapter.pyi +0 -0
  108. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/bases/__init__.py +0 -0
  109. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/bases/__init__.pyi +0 -0
  110. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/bases/finder.py +0 -0
  111. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/bases/finder.pyi +0 -0
  112. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/module.py +0 -0
  113. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/finders/module.pyi +0 -0
  114. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/__init__.py +0 -0
  115. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/__init__.pyi +0 -0
  116. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/adapter.py +0 -0
  117. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/adapter.pyi +0 -0
  118. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/bases/__init__.py +0 -0
  119. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/bases/__init__.pyi +0 -0
  120. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/bases/loader.py +0 -0
  121. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/bases/loader.pyi +0 -0
  122. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/module.py +0 -0
  123. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/module.pyi +0 -0
  124. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/strategy.py +0 -0
  125. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/loaders/strategy.pyi +0 -0
  126. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/__init__.py +0 -0
  127. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/__init__.pyi +0 -0
  128. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/config_schema.py +0 -0
  129. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/config_schema.pyi +0 -0
  130. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/context.py +0 -0
  131. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/context.pyi +0 -0
  132. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/exceptions.py +0 -0
  133. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/exceptions.pyi +0 -0
  134. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/frame_config.py +0 -0
  135. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/runtime/frame_config.pyi +0 -0
  136. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/sdk.py +0 -0
  137. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/sdk.pyi +0 -0
  138. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/web_status/4xx.png +0 -0
  139. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/web_status/5xx.png +0 -0
  140. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/web_status/__init__.py +0 -0
  141. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/web_status/__init__.pyi +0 -0
  142. {erispulse-2.4.6.dev6 → erispulse-2.4.8}/src/ErisPulse/web_status/unknow.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse
3
- Version: 2.4.6.dev6
3
+ Version: 2.4.8
4
4
  Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
5
5
  Author-email: ErisDev <erisdev@88.com>
6
6
  Maintainer-email: "艾莉丝·格雷拉特(WSu2059)" <wsu2059@qq.com>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ErisPulse"
7
- version = "2.4.6-dev.6"
7
+ version = "2.4.8"
8
8
  description = "ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -73,7 +73,7 @@ class HttpResponse(BaseHttpResponse):
73
73
  >>> data = await resp.json()
74
74
  """
75
75
 
76
- __slots__ = ("_response", "_body", "_body_read")
76
+ __slots__ = ("_response", "_body", "_body_read", "_released")
77
77
 
78
78
  def __init__(self, response):
79
79
  """
@@ -82,6 +82,7 @@ class HttpResponse(BaseHttpResponse):
82
82
  self._response = response
83
83
  self._body: bytes | None = None
84
84
  self._body_read = False
85
+ self._released = False
85
86
 
86
87
  @property
87
88
  def status(self) -> int:
@@ -179,7 +180,9 @@ class HttpResponse(BaseHttpResponse):
179
180
  return self
180
181
 
181
182
  async def __aexit__(self, *args):
182
- self._response.release()
183
+ if not self._released:
184
+ self._response.release()
185
+ self._released = True
183
186
 
184
187
 
185
188
  class ClientWebSocket(BaseClientWebSocket):
@@ -201,13 +204,14 @@ class ClientWebSocket(BaseClientWebSocket):
201
204
  ... await ws.send_text(f"Echo: {text}")
202
205
  """
203
206
 
204
- __slots__ = ()
207
+ __slots__ = ("_recv_lock",)
205
208
 
206
209
  def __init__(self, ws):
207
210
  """
208
211
  :param ws: aiohttp.ClientWebSocketResponse 底层 aiohttp WS 对象
209
212
  """
210
213
  super().__init__(ws)
214
+ self._recv_lock = asyncio.Lock()
211
215
 
212
216
  @property
213
217
  def closed(self) -> bool:
@@ -226,6 +230,8 @@ class ClientWebSocket(BaseClientWebSocket):
226
230
 
227
231
  :param data: str 文本内容
228
232
  """
233
+ if self._ws.closed:
234
+ raise WebSocketError("WebSocket is closed")
229
235
  await self._ws.send_str(data)
230
236
 
231
237
  async def send_bytes(self, data: bytes) -> None:
@@ -234,6 +240,8 @@ class ClientWebSocket(BaseClientWebSocket):
234
240
 
235
241
  :param data: bytes 二进制内容
236
242
  """
243
+ if self._ws.closed:
244
+ raise WebSocketError("WebSocket is closed")
237
245
  await self._ws.send_bytes(data)
238
246
 
239
247
  async def send_json(self, data: Any, mode: str = "text") -> None:
@@ -243,7 +251,13 @@ class ClientWebSocket(BaseClientWebSocket):
243
251
  :param data: Any 要序列化的数据
244
252
  :param mode: str 发送模式 ("text" 或 "binary") (默认: "text")
245
253
  """
246
- await self._ws.send_json(data)
254
+ if self._ws.closed:
255
+ raise WebSocketError("WebSocket is closed")
256
+ if mode == "binary":
257
+ payload = json.dumps(data).encode("utf-8")
258
+ await self._ws.send_bytes(payload)
259
+ else:
260
+ await self._ws.send_json(data)
247
261
 
248
262
  # ---- Receive ----
249
263
 
@@ -277,7 +291,8 @@ class ClientWebSocket(BaseClientWebSocket):
277
291
 
278
292
  :return: WSMessage 消息对象
279
293
  """
280
- msg = await self._ws.receive()
294
+ async with self._recv_lock:
295
+ msg = await self._ws.receive()
281
296
  return self._convert_ws_msg(msg)
282
297
 
283
298
  async def receive_text(self) -> str:
@@ -290,7 +305,8 @@ class ClientWebSocket(BaseClientWebSocket):
290
305
  """
291
306
  import aiohttp
292
307
 
293
- msg = await self._ws.receive()
308
+ async with self._recv_lock:
309
+ msg = await self._ws.receive()
294
310
  if msg.type == aiohttp.WSMsgType.TEXT:
295
311
  return msg.data
296
312
  elif msg.type in (
@@ -315,7 +331,8 @@ class ClientWebSocket(BaseClientWebSocket):
315
331
  """
316
332
  import aiohttp
317
333
 
318
- msg = await self._ws.receive()
334
+ async with self._recv_lock:
335
+ msg = await self._ws.receive()
319
336
  if msg.type == aiohttp.WSMsgType.BINARY:
320
337
  return msg.data
321
338
  elif msg.type in (
@@ -421,6 +438,7 @@ class HttpClient(BaseHttpClient):
421
438
  )
422
439
  self._session = None
423
440
  self._ws_session = None
441
+ self._session_lock = asyncio.Lock()
424
442
  self._stats = {
425
443
  "total_requests": 0,
426
444
  "total_errors": 0,
@@ -431,39 +449,58 @@ class HttpClient(BaseHttpClient):
431
449
  # ---- Session 管理 ----
432
450
 
433
451
  async def _get_ws_session(self):
434
- if self._ws_session is None or self._ws_session.closed:
435
- import aiohttp
436
-
437
- self._ws_session = aiohttp.ClientSession(
438
- timeout=aiohttp.ClientTimeout(),
439
- )
440
- return self._ws_session
441
-
442
- def _drain_sessions(self):
443
- self._session = None
444
- self._ws_session = None
452
+ async with self._session_lock:
453
+ if self._ws_session is None or self._ws_session.closed:
454
+ import aiohttp
455
+
456
+ self._ws_session = aiohttp.ClientSession(
457
+ timeout=aiohttp.ClientTimeout(),
458
+ headers=self._default_headers or None,
459
+ )
460
+ return self._ws_session
461
+
462
+ async def _drain_sessions(self):
463
+ async with self._session_lock:
464
+ old_session = self._session
465
+ old_ws = self._ws_session
466
+ self._session = None
467
+ self._ws_session = None
468
+ if old_session and not old_session.closed:
469
+ try:
470
+ await old_session.close()
471
+ except Exception:
472
+ pass
473
+ if old_ws and not old_ws.closed:
474
+ try:
475
+ await old_ws.close()
476
+ except Exception:
477
+ pass
445
478
 
446
479
  async def _get_http_session(self):
447
- if self._session is None or self._session.closed:
448
- import aiohttp
449
-
450
- timeout = aiohttp.ClientTimeout(
451
- total=self._timeout,
452
- connect=self._connect_timeout,
453
- )
454
- self._session = aiohttp.ClientSession(
455
- timeout=timeout,
456
- headers=self._default_headers,
457
- )
458
- return self._session
480
+ async with self._session_lock:
481
+ if self._session is None or self._session.closed:
482
+ import aiohttp
483
+
484
+ timeout = aiohttp.ClientTimeout(
485
+ total=self._timeout,
486
+ connect=self._connect_timeout,
487
+ )
488
+ self._session = aiohttp.ClientSession(
489
+ timeout=timeout,
490
+ headers=self._default_headers,
491
+ )
492
+ return self._session
459
493
 
460
494
  async def close(self) -> None:
461
- if self._session and not self._session.closed:
462
- await self._session.close()
495
+ async with self._session_lock:
496
+ old_session = self._session
497
+ old_ws = self._ws_session
463
498
  self._session = None
464
- if self._ws_session and not self._ws_session.closed:
465
- await self._ws_session.close()
466
499
  self._ws_session = None
500
+ if old_session and not old_session.closed:
501
+ await old_session.close()
502
+ if old_ws and not old_ws.closed:
503
+ await old_ws.close()
467
504
 
468
505
  # ---- 核心请求方法 ----
469
506
 
@@ -529,6 +566,8 @@ class HttpClient(BaseHttpClient):
529
566
  **kwargs,
530
567
  ) as resp:
531
568
  response = HttpResponse(resp)
569
+ await response._eager_read()
570
+ response._released = True
532
571
  elapsed = time.monotonic() - start
533
572
 
534
573
  self._stats["total_requests"] += 1
@@ -544,38 +583,50 @@ class HttpClient(BaseHttpClient):
544
583
 
545
584
  return response
546
585
 
547
- except ClientConnectionError as e:
548
- last_exc = e
586
+ except asyncio.TimeoutError as e:
587
+ last_exc = _convert_aiohttp_exception(e)
588
+ self._stats["total_errors"] += 1
589
+ elapsed = time.monotonic() - start
590
+ if attempt < retries:
591
+ logger.debug(
592
+ f"[HttpClient] {method} {url} 超时 (尝试 {attempt + 1}/{retries + 1})"
593
+ )
594
+ await asyncio.sleep(self._retry_delay)
595
+ else:
596
+ logger.error(
597
+ f"[HttpClient] {method} {url} 最终超时 ({elapsed:.3f}s)"
598
+ )
599
+ except aiohttp.ClientConnectionError as e:
600
+ last_exc = _convert_aiohttp_exception(e)
549
601
  self._stats["total_errors"] += 1
550
602
  elapsed = time.monotonic() - start
551
603
  if attempt < retries:
552
604
  logger.debug(
553
605
  f"[HttpClient] {method} {url} 连接中断,重建 session 后重试 (尝试 {attempt + 1}/{retries + 1})"
554
606
  )
555
- self._drain_sessions()
607
+ await self._drain_sessions()
556
608
  await asyncio.sleep(self._retry_delay)
557
609
  else:
558
610
  logger.error(
559
611
  f"[HttpClient] {method} {url} 最终连接失败 ({elapsed:.3f}s)"
560
612
  )
561
- except asyncio.TimeoutError as e:
613
+ except aiohttp.ClientError as e:
562
614
  last_exc = _convert_aiohttp_exception(e)
563
615
  self._stats["total_errors"] += 1
564
616
  elapsed = time.monotonic() - start
565
617
  if attempt < retries:
566
618
  logger.debug(
567
- f"[HttpClient] {method} {url} 超时 (尝试 {attempt + 1}/{retries + 1})"
619
+ f"[HttpClient] {method} {url} 失败 (尝试 {attempt + 1}/{retries + 1}): {e}"
568
620
  )
569
621
  await asyncio.sleep(self._retry_delay)
570
622
  else:
571
623
  logger.error(
572
- f"[HttpClient] {method} {url} 最终超时 ({elapsed:.3f}s)"
624
+ f"[HttpClient] {method} {url} 最终失败 ({elapsed:.3f}s): {e}"
573
625
  )
626
+ except ClientError:
627
+ raise
574
628
  except Exception as e:
575
- if isinstance(e, aiohttp.ClientError):
576
- last_exc = _convert_aiohttp_exception(e)
577
- else:
578
- last_exc = ClientError(str(e))
629
+ last_exc = ClientError(str(e))
579
630
  self._stats["total_errors"] += 1
580
631
  elapsed = time.monotonic() - start
581
632
  if attempt < retries:
@@ -253,7 +253,7 @@ class HttpClient(BaseHttpClient):
253
253
  ...
254
254
  async def _get_ws_session(self: object) -> ...:
255
255
  ...
256
- def _drain_sessions(self: object) -> ...:
256
+ async def _drain_sessions(self: object) -> ...:
257
257
  ...
258
258
  async def _get_http_session(self: object) -> ...:
259
259
  ...
File without changes
File without changes
File without changes