by-framework 0.2.1__tar.gz → 0.2.2.dev0__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 (202) hide show
  1. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/workflows/publish.yml +27 -7
  2. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/PKG-INFO +1 -1
  3. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/pyproject.toml +1 -1
  4. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-byclaw/pyproject.toml +1 -1
  5. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-postgres/pyproject.toml +1 -1
  6. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/pyproject.toml +1 -1
  7. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-langfuse/pyproject.toml +1 -1
  8. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-phoenix/pyproject.toml +1 -1
  9. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/pyproject.toml +1 -1
  10. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/util/discovery_http_client.py +34 -9
  11. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/util/http_client.py +27 -6
  12. by_framework-0.2.2.dev0/tests/util/test_discovery_http_client_upload.py +181 -0
  13. by_framework-0.2.2.dev0/tests/util/test_http_client.py +172 -0
  14. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/uv.lock +7 -7
  15. by_framework-0.2.1/tests/util/test_http_client.py +0 -41
  16. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  17. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  18. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  19. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/RELEASING.md +0 -0
  20. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/workflows/ci.yml +0 -0
  21. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.github/workflows/stale.yml +0 -0
  22. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.gitignore +0 -0
  23. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/.pre-commit-config.yaml +0 -0
  24. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/AGENTS.md +0 -0
  25. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/CHANGELOG.md +0 -0
  26. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/CLAUDE.md +0 -0
  27. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/CODE_OF_CONDUCT.md +0 -0
  28. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/CONTRIBUTING.md +0 -0
  29. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/LICENSE +0 -0
  30. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/Makefile +0 -0
  31. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/README.md +0 -0
  32. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/README_zh.md +0 -0
  33. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/SECURITY.md +0 -0
  34. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/assets/img/architecture_en.png +0 -0
  35. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/assets/img/architecture_zh.png +0 -0
  36. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/autoformat.sh +0 -0
  37. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/docs/README.md +0 -0
  38. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  39. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/README.md +0 -0
  40. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  41. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  42. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
  43. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  44. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-adk/tests/test_worker.py +0 -0
  45. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-byclaw/README.md +0 -0
  46. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
  47. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
  48. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  49. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-postgres/README.md +0 -0
  50. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
  51. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
  52. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
  53. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/README.md +0 -0
  54. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  55. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  56. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
  57. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  58. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  59. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
  60. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  61. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  62. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-langfuse/README.md +0 -0
  63. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
  64. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
  65. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  66. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-phoenix/README.md +0 -0
  67. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
  68. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
  69. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  70. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/pylintrc +0 -0
  71. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/scripts/python_quality.sh +0 -0
  72. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/__init__.py +0 -0
  73. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/__main__.py +0 -0
  74. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/client/__init__.py +0 -0
  75. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/client/byai_client.py +0 -0
  76. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/client/client.py +0 -0
  77. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/__init__.py +0 -0
  78. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/config.py +0 -0
  79. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/constants.py +0 -0
  80. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/emitter.py +0 -0
  81. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/exceptions.py +0 -0
  82. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/logger.py +0 -0
  83. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/common/redis_client.py +0 -0
  84. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/__init__.py +0 -0
  85. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/availability.py +0 -0
  86. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/delivery_gate.py +0 -0
  87. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/discovery.py +0 -0
  88. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/extensions/__init__.py +0 -0
  89. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/extensions/agent_config.py +0 -0
  90. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/extensions/plugin.py +0 -0
  91. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/extensions/registry.py +0 -0
  92. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/extensions/trace_provider.py +0 -0
  93. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/__init__.py +0 -0
  94. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/action_type.py +0 -0
  95. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/agent_state.py +0 -0
  96. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/byai_codec.py +0 -0
  97. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/byai_command.py +0 -0
  98. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/byai_types.py +0 -0
  99. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/commands.py +0 -0
  100. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/content_codec.py +0 -0
  101. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/content_type.py +0 -0
  102. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/data_message.py +0 -0
  103. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/data_shapes.py +0 -0
  104. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/event_type.py +0 -0
  105. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/events.py +0 -0
  106. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/message.py +0 -0
  107. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/message_header.py +0 -0
  108. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/responses.py +0 -0
  109. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/protocol/results.py +0 -0
  110. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/registry.py +0 -0
  111. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/__init__.py +0 -0
  112. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
  113. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
  114. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/file_manager.py +0 -0
  115. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/file_paths.py +0 -0
  116. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/file_permissions.py +0 -0
  117. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
  118. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/filestore/base.py +0 -0
  119. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/filestore/local.py +0 -0
  120. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/history/__init__.py +0 -0
  121. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/history/base.py +0 -0
  122. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/history/history_manager.py +0 -0
  123. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/history/in_memory.py +0 -0
  124. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/runtime/session_manager.py +0 -0
  125. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/wakeup_controller.py +0 -0
  126. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/core/workspace.py +0 -0
  127. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/__init__.py +0 -0
  128. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/base.py +0 -0
  129. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/common.py +0 -0
  130. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/execution.py +0 -0
  131. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/http.py +0 -0
  132. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/protocol.py +0 -0
  133. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/errors/registry.py +0 -0
  134. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/util/__init__.py +0 -0
  135. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/util/generate_message_id.py +0 -0
  136. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/__init__.py +0 -0
  137. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/_control_handling.py +0 -0
  138. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/_execution_tracking.py +0 -0
  139. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/_message_processing.py +0 -0
  140. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/app.py +0 -0
  141. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/byai_context.py +0 -0
  142. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/byai_worker.py +0 -0
  143. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/context.py +0 -0
  144. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/heartbeat.py +0 -0
  145. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/processor.py +0 -0
  146. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/runner.py +0 -0
  147. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/sandbox/__init__.py +0 -0
  148. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  149. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/src/by_framework/worker/worker.py +0 -0
  150. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/client/__init__.py +0 -0
  151. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/client/test_client.py +0 -0
  152. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/common/__init__.py +0 -0
  153. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/common/test_config.py +0 -0
  154. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/common/test_exceptions.py +0 -0
  155. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/common/test_logger.py +0 -0
  156. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/common/test_redis_client.py +0 -0
  157. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/conftest.py +0 -0
  158. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/__init__.py +0 -0
  159. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/protocol/__init__.py +0 -0
  160. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/protocol/test_byai_codec.py +0 -0
  161. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/protocol/test_command_wire.py +0 -0
  162. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/protocol/test_protocol.py +0 -0
  163. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/protocol/test_results.py +0 -0
  164. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/history/__init__.py +0 -0
  165. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/history/test_history_persistence.py +0 -0
  166. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_file_access_context.py +0 -0
  167. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
  168. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_file_paths.py +0 -0
  169. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_file_permissions.py +0 -0
  170. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_filestore_local_collection.py +0 -0
  171. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
  172. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_filestore_local_read.py +0 -0
  173. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/runtime/test_filestore_local_search.py +0 -0
  174. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/test_availability.py +0 -0
  175. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/test_discovery.py +0 -0
  176. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/core/test_registry.py +0 -0
  177. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/integration/__init__.py +0 -0
  178. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/integration/test_ask_user_flow.py +0 -0
  179. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/integration/test_callback_flow.py +0 -0
  180. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/integration/test_logger_integration.py +0 -0
  181. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/integration/test_scatter_gather.py +0 -0
  182. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/plugin/__init__.py +0 -0
  183. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/plugin/test_langfuse_plugin.py +0 -0
  184. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/plugin/test_plugin_discovery.py +0 -0
  185. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/plugin/test_plugin_improvements.py +0 -0
  186. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/plugin/test_plugin_registry.py +0 -0
  187. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/plugin/test_plugin_system.py +0 -0
  188. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/util/test_discovery_http_client.py +0 -0
  189. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/util/test_discovery_http_client_download.py +0 -0
  190. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/__init__.py +0 -0
  191. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_app.py +0 -0
  192. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_byai_worker.py +0 -0
  193. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_context.py +0 -0
  194. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_control_handling.py +0 -0
  195. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_emitter.py +0 -0
  196. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_gateway_worker.py +0 -0
  197. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_heartbeat.py +0 -0
  198. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_message_processing.py +0 -0
  199. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_processor.py +0 -0
  200. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_runner.py +0 -0
  201. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_sandbox.py +0 -0
  202. {by_framework-0.2.1 → by_framework-0.2.2.dev0}/tests/worker/test_workspace.py +0 -0
@@ -63,7 +63,7 @@ jobs:
63
63
  echo "package_name=by-framework-adk" >> "$GITHUB_OUTPUT"
64
64
  echo "package_dir=libs/by-framework-adk" >> "$GITHUB_OUTPUT"
65
65
  echo "version=${REF_NAME#by-framework-adk-v}" >> "$GITHUB_OUTPUT"
66
- ;;
66
+ ;;
67
67
  *)
68
68
  echo "Unsupported tag: $REF_NAME" >&2
69
69
  exit 1
@@ -152,6 +152,10 @@ jobs:
152
152
  ref: ${{ github.ref }}
153
153
  fetch-depth: 0
154
154
  token: ${{ secrets.GITHUB_TOKEN }}
155
+ - uses: actions/setup-python@v5
156
+ with:
157
+ python-version: "3.12"
158
+ - uses: astral-sh/setup-uv@v5
155
159
 
156
160
  - name: Create release branch
157
161
  env:
@@ -169,20 +173,36 @@ jobs:
169
173
  PACKAGE_DIR: ${{ needs.resolve-package.outputs.package_dir }}
170
174
  VERSION: ${{ needs.resolve-package.outputs.version }}
171
175
  run: |
172
- # Calculate next dev version (e.g., 0.2.1 -> 0.2.2.dev0)
173
- IFS='.' read -ra PARTS <<< "$VERSION"
174
- LAST_INDEX=$((${#PARTS[@]} - 1))
175
- PARTS[$LAST_INDEX]=$((PARTS[$LAST_INDEX] + 1))
176
- NEXT_VERSION=$(IFS='.'; echo "${PARTS[*]}").dev0
176
+ # Calculate next version using Python to support pre-releases (e.g., 0.2.3b0 -> 0.2.3b1, 0.2.3 -> 0.2.4.dev0)
177
+ NEXT_VERSION=$(python -c 'if True:
178
+ import re, sys
179
+ version = sys.argv[1]
180
+ match = re.search(r"(a|b|rc|dev)(\d+)$", version)
181
+ if match:
182
+ prefix = version[:match.start()]
183
+ suffix_type = match.group(1)
184
+ suffix_num = int(match.group(2))
185
+ print(f"{prefix}{suffix_type}{suffix_num + 1}")
186
+ else:
187
+ parts = version.split(".")
188
+ try:
189
+ parts[-1] = str(int(parts[-1]) + 1)
190
+ print(".".join(parts) + ".dev0")
191
+ except ValueError:
192
+ print(version + ".dev0")
193
+ ' "$VERSION")
177
194
 
178
195
  PYPROJECT="${PACKAGE_DIR}/pyproject.toml"
179
196
  sed -i "s/version = \"${VERSION}\"/version = \"${NEXT_VERSION}\"/" "$PYPROJECT"
180
197
 
198
+ # Regenerate uv.lock to align with the new version bump in workspace
199
+ uv lock
200
+
181
201
  BUMP_BRANCH="bump/${PACKAGE_NAME}/v${NEXT_VERSION}"
182
202
  git checkout -b "$BUMP_BRANCH"
183
203
  git config user.name "github-actions[bot]"
184
204
  git config user.email "github-actions[bot]@users.noreply.github.com"
185
- git add "$PYPROJECT"
205
+ git add "$PYPROJECT" uv.lock
186
206
  git commit -m "chore: bump ${PACKAGE_NAME} version to ${NEXT_VERSION}"
187
207
  git push origin "$BUMP_BRANCH"
188
208
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: by-framework
3
- Version: 0.2.1
3
+ Version: 0.2.2.dev0
4
4
  Summary: 分布式 Agent 调度框架
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-adk"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "ADK integration for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-history-byclaw"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "ByClaw remote history backend for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-history-postgres"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "PostgreSQL history backend for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-langgraph"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "LangGraph integration for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-trace-langfuse"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "Langfuse trace provider for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework-trace-phoenix"
3
- version = "0.0.2"
3
+ version = "0.0.3.dev0"
4
4
  description = "Arize Phoenix trace provider for by-framework"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework"
3
- version = "0.2.1"
3
+ version = "0.2.2.dev0"
4
4
  description = "分布式 Agent 调度框架"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -379,14 +379,17 @@ class DiscoveryHttpClient:
379
379
  Returns:
380
380
  HttpResponse from the server
381
381
  """
382
+ p = Path(file_path)
382
383
  parts: list[tuple[str, Any]] = []
383
384
  if form_fields:
384
385
  for key, value in form_fields.items():
385
386
  parts.append((key, value))
386
- parts.append((file_field, str(file_path)))
387
- return await self._upload_with_discovery(
388
- service_name, path, parts, headers=headers
389
- )
387
+
388
+ with open(p, "rb") as f:
389
+ parts.append((file_field, (p.name, f)))
390
+ return await self._upload_with_discovery(
391
+ service_name, path, parts, headers=headers
392
+ )
390
393
 
391
394
  async def upload_multiple(
392
395
  self,
@@ -412,15 +415,21 @@ class DiscoveryHttpClient:
412
415
  Returns:
413
416
  HttpResponse from the server
414
417
  """
418
+ from contextlib import ExitStack
419
+
415
420
  parts: list[tuple[str, Any]] = []
416
421
  if form_fields:
417
422
  for key, value in form_fields.items():
418
423
  parts.append((key, value))
419
- for fp in file_paths:
420
- parts.append((file_field, str(fp)))
421
- return await self._upload_with_discovery(
422
- service_name, path, parts, headers=headers
423
- )
424
+
425
+ with ExitStack() as stack:
426
+ for fp in file_paths:
427
+ p = Path(fp)
428
+ f = stack.enter_context(open(p, "rb"))
429
+ parts.append((file_field, (p.name, f)))
430
+ return await self._upload_with_discovery(
431
+ service_name, path, parts, headers=headers
432
+ )
424
433
 
425
434
  async def upload_with_stream(
426
435
  self,
@@ -537,6 +546,22 @@ class DiscoveryHttpClient:
537
546
  logger.warning(
538
547
  "Node-switching retry in %.1fs for service %s", delay, service_name
539
548
  )
549
+
550
+ # Reset seek position for any file-like objects in parts before retrying
551
+ for _, val in parts:
552
+ if isinstance(val, tuple):
553
+ for item in val:
554
+ if hasattr(item, "seek") and callable(item.seek):
555
+ try:
556
+ item.seek(0)
557
+ except Exception:
558
+ pass
559
+ elif hasattr(val, "seek") and callable(val.seek):
560
+ try:
561
+ val.seek(0)
562
+ except Exception:
563
+ pass
564
+
540
565
  await asyncio.sleep(delay)
541
566
  return await self._upload_with_discovery(
542
567
  service_name,
@@ -543,12 +543,15 @@ class ByHttpClient:
543
543
  Returns:
544
544
  HttpResponse from the server
545
545
  """
546
+ path = Path(file_path)
546
547
  parts: list[tuple[str, Any]] = []
547
548
  if form_fields:
548
549
  for key, value in form_fields.items():
549
550
  parts.append((key, value))
550
- parts.append((file_field, str(file_path)))
551
- return await self._upload(url, parts, headers=headers)
551
+
552
+ with open(path, "rb") as f:
553
+ parts.append((file_field, (path.name, f)))
554
+ return await self._upload(url, parts, headers=headers)
552
555
 
553
556
  async def upload_multiple(
554
557
  self,
@@ -572,13 +575,19 @@ class ByHttpClient:
572
575
  Returns:
573
576
  HttpResponse from the server
574
577
  """
578
+ from contextlib import ExitStack
579
+
575
580
  parts: list[tuple[str, Any]] = []
576
581
  if form_fields:
577
582
  for key, value in form_fields.items():
578
583
  parts.append((key, value))
579
- for path in file_paths:
580
- parts.append((file_field, str(path)))
581
- return await self._upload(url, parts, headers=headers)
584
+
585
+ with ExitStack() as stack:
586
+ for fp in file_paths:
587
+ path = Path(fp)
588
+ f = stack.enter_context(open(path, "rb"))
589
+ parts.append((file_field, (path.name, f)))
590
+ return await self._upload(url, parts, headers=headers)
582
591
 
583
592
  async def upload_with_stream(
584
593
  self,
@@ -643,12 +652,24 @@ class ByHttpClient:
643
652
 
644
653
  logger.debug("[POST] %s (multipart upload, %d parts)", url, len(parts))
645
654
 
655
+ # Convert non-file parts (e.g. standard form fields) to (None, value)
656
+ # to ensure httpx renders them without filename attribute,
657
+ # and avoid AsyncClient sync/async conflicts.
658
+ formatted_parts: list[tuple[str, Any]] = []
659
+ for key, val in parts:
660
+ if isinstance(val, tuple):
661
+ formatted_parts.append((key, val))
662
+ elif hasattr(val, "read") and callable(val.read):
663
+ formatted_parts.append((key, val))
664
+ else:
665
+ formatted_parts.append((key, (None, str(val))))
666
+
646
667
  try:
647
668
  response = await self._client.request(
648
669
  method="POST",
649
670
  url=url,
650
671
  headers=request_headers,
651
- files=parts,
672
+ files=formatted_parts,
652
673
  )
653
674
 
654
675
  logger.debug(
@@ -0,0 +1,181 @@
1
+ import io
2
+ from unittest.mock import AsyncMock, MagicMock
3
+
4
+ import pytest
5
+
6
+ from by_framework.core.discovery import DiscoveryClient, ServiceInstance
7
+ from by_framework.util.discovery_http_client import DiscoveryHttpClient
8
+ from by_framework.util.http_client import (ByHttpClient, HttpResponse, RetryConfig)
9
+
10
+
11
+ @pytest.fixture
12
+ def mock_discovery_client():
13
+ client = MagicMock(spec=DiscoveryClient)
14
+ client.discover = AsyncMock()
15
+ return client
16
+
17
+
18
+ @pytest.fixture
19
+ def mock_http_client():
20
+ client = MagicMock(spec=ByHttpClient)
21
+ client._upload = AsyncMock()
22
+ return client
23
+
24
+
25
+ @pytest.fixture
26
+ def fake_instance():
27
+ return ServiceInstance(id="inst1", host="192.168.1.100", port=8080)
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ async def test_discovery_upload_single_file(
32
+ mock_discovery_client, mock_http_client, fake_instance, tmp_path
33
+ ):
34
+ mock_discovery_client.discover.return_value = fake_instance
35
+ success_response = HttpResponse(
36
+ status_code=200, headers={}, data={"status": "ok"}, is_success=True
37
+ )
38
+ mock_http_client._upload.return_value = success_response
39
+
40
+ client = DiscoveryHttpClient(
41
+ discovery_client=mock_discovery_client,
42
+ http_client=mock_http_client,
43
+ )
44
+
45
+ file_path = tmp_path / "test.txt"
46
+ file_path.write_bytes(b"hello")
47
+
48
+ response = await client.upload(
49
+ service_name="my-service",
50
+ path="/upload",
51
+ file_path=file_path,
52
+ form_fields={"field1": "value1"},
53
+ )
54
+
55
+ assert response.is_success is True
56
+ mock_http_client._upload.assert_called_once()
57
+ called_args, _ = mock_http_client._upload.call_args
58
+ assert called_args[0] == "http://192.168.1.100:8080/upload"
59
+ parts = called_args[1]
60
+
61
+ assert ("field1", "value1") in parts
62
+ file_part = [p for p in parts if p[0] == "file"][0]
63
+ assert file_part[1][0] == "test.txt"
64
+ assert not isinstance(file_part[1][1], str)
65
+ assert hasattr(file_part[1][1], "read")
66
+
67
+
68
+ @pytest.mark.asyncio
69
+ async def test_discovery_upload_multiple_files(
70
+ mock_discovery_client, mock_http_client, fake_instance, tmp_path
71
+ ):
72
+ mock_discovery_client.discover.return_value = fake_instance
73
+ success_response = HttpResponse(
74
+ status_code=200, headers={}, data={"status": "ok"}, is_success=True
75
+ )
76
+ mock_http_client._upload.return_value = success_response
77
+
78
+ client = DiscoveryHttpClient(
79
+ discovery_client=mock_discovery_client,
80
+ http_client=mock_http_client,
81
+ )
82
+
83
+ f1 = tmp_path / "f1.txt"
84
+ f2 = tmp_path / "f2.txt"
85
+ f1.write_bytes(b"1")
86
+ f2.write_bytes(b"2")
87
+
88
+ response = await client.upload_multiple(
89
+ service_name="my-service",
90
+ path="/upload",
91
+ file_paths=[f1, f2],
92
+ )
93
+
94
+ assert response.is_success is True
95
+ mock_http_client._upload.assert_called_once()
96
+ called_args, _ = mock_http_client._upload.call_args
97
+ parts = called_args[1]
98
+
99
+ file_parts = [p for p in parts if p[0] == "file"]
100
+ assert len(file_parts) == 2
101
+ assert file_parts[0][1][0] == "f1.txt"
102
+ assert file_parts[1][1][0] == "f2.txt"
103
+
104
+
105
+ @pytest.mark.asyncio
106
+ async def test_discovery_upload_with_stream(
107
+ mock_discovery_client, mock_http_client, fake_instance
108
+ ):
109
+ mock_discovery_client.discover.return_value = fake_instance
110
+ success_response = HttpResponse(
111
+ status_code=200, headers={}, data={"status": "ok"}, is_success=True
112
+ )
113
+ mock_http_client._upload.return_value = success_response
114
+
115
+ client = DiscoveryHttpClient(
116
+ discovery_client=mock_discovery_client,
117
+ http_client=mock_http_client,
118
+ )
119
+
120
+ response = await client.upload_with_stream(
121
+ service_name="my-service",
122
+ path="/upload",
123
+ file_name="stream.bin",
124
+ content=b"stream content",
125
+ )
126
+
127
+ assert response.is_success is True
128
+ mock_http_client._upload.assert_called_once()
129
+ called_args, _ = mock_http_client._upload.call_args
130
+ parts = called_args[1]
131
+ file_part = [p for p in parts if p[0] == "file"][0]
132
+ assert file_part[1][0] == "stream.bin"
133
+ assert file_part[1][2] == "application/octet-stream"
134
+ assert isinstance(file_part[1][1], io.BytesIO)
135
+
136
+
137
+ @pytest.mark.asyncio
138
+ async def test_discovery_upload_retry_resets_stream_seeks(
139
+ mock_discovery_client, mock_http_client, fake_instance
140
+ ):
141
+ mock_discovery_client.discover.return_value = fake_instance
142
+
143
+ fail_response = HttpResponse(
144
+ status_code=502, headers={}, data="Error", is_success=False
145
+ )
146
+ success_response = HttpResponse(
147
+ status_code=200, headers={}, data="Success", is_success=True
148
+ )
149
+
150
+ async def mock_upload_handler(url, parts, headers=None):
151
+ file_part = [p for p in parts if p[0] == "file"][0]
152
+ file_stream = file_part[1][1]
153
+
154
+ data = file_stream.read()
155
+ assert data == b"test bytes"
156
+
157
+ if mock_upload_handler.call_count == 0:
158
+ mock_upload_handler.call_count += 1
159
+ return fail_response
160
+ return success_response
161
+
162
+ mock_upload_handler.call_count = 0
163
+ mock_http_client._upload.side_effect = mock_upload_handler
164
+
165
+ client = DiscoveryHttpClient(
166
+ discovery_client=mock_discovery_client,
167
+ http_client=mock_http_client,
168
+ retry_config=RetryConfig(
169
+ max_attempts=2, retry_on_status_codes=frozenset({502})
170
+ ),
171
+ )
172
+
173
+ response = await client.upload_with_stream(
174
+ service_name="my-service",
175
+ path="/upload",
176
+ file_name="stream.bin",
177
+ content=b"test bytes",
178
+ )
179
+
180
+ assert response.is_success is True
181
+ assert mock_http_client._upload.call_count == 2
@@ -0,0 +1,172 @@
1
+ """Tests for file download support in ByHttpClient."""
2
+
3
+ from pathlib import Path
4
+
5
+ import httpx
6
+ import pytest
7
+
8
+ from by_framework.util.http_client import ByHttpClient, RetryConfig
9
+
10
+
11
+ @pytest.mark.asyncio
12
+ async def test_download_streams_response_to_file(tmp_path: Path):
13
+ payload = b"binary payload for download"
14
+
15
+ async def handler(request: httpx.Request) -> httpx.Response:
16
+ assert request.method == "GET"
17
+ assert request.url.path == "/files/archive.bin"
18
+ return httpx.Response(
19
+ status_code=200,
20
+ content=payload,
21
+ headers={"content-type": "application/octet-stream"},
22
+ request=request,
23
+ )
24
+
25
+ target_path = tmp_path / "archive.bin"
26
+ transport = httpx.MockTransport(handler)
27
+
28
+ async with ByHttpClient(
29
+ base_url="https://example.com",
30
+ http_client=httpx.AsyncClient(
31
+ transport=transport,
32
+ base_url="https://example.com",
33
+ ),
34
+ retry_config=RetryConfig.no_retry(),
35
+ ) as client:
36
+ response = await client.download("/files/archive.bin", target_path)
37
+
38
+ assert response.is_success is True
39
+ assert response.status_code == 200
40
+ assert response.data == str(target_path)
41
+ assert target_path.read_bytes() == payload
42
+
43
+
44
+ @pytest.mark.asyncio
45
+ async def test_upload_single_file(tmp_path: Path):
46
+ file_content = b"my unique file content"
47
+ file_path = tmp_path / "test_upload.txt"
48
+ file_path.write_bytes(file_content)
49
+
50
+ async def handler(request: httpx.Request) -> httpx.Response:
51
+ assert request.method == "POST"
52
+ assert request.url.path == "/upload"
53
+ req_body = await request.aread()
54
+
55
+ # 验证是否上传了真正的文件内容
56
+ assert file_content in req_body
57
+ assert b'filename="test_upload.txt"' in req_body
58
+ assert b'name="file"' in req_body
59
+ assert b'name="field1"' in req_body
60
+ assert b"value1" in req_body
61
+
62
+ return httpx.Response(
63
+ status_code=200,
64
+ json={"status": "uploaded"},
65
+ request=request,
66
+ )
67
+
68
+ transport = httpx.MockTransport(handler)
69
+
70
+ async with ByHttpClient(
71
+ base_url="https://example.com",
72
+ http_client=httpx.AsyncClient(
73
+ transport=transport,
74
+ base_url="https://example.com",
75
+ ),
76
+ retry_config=RetryConfig.no_retry(),
77
+ ) as client:
78
+ response = await client.upload(
79
+ "/upload",
80
+ file_path,
81
+ file_field="file",
82
+ form_fields={"field1": "value1"},
83
+ )
84
+
85
+ assert response.is_success is True
86
+ assert response.data == {"status": "uploaded"}
87
+
88
+
89
+ @pytest.mark.asyncio
90
+ async def test_upload_multiple_files(tmp_path: Path):
91
+ file1_content = b"content of file 1"
92
+ file2_content = b"content of file 2"
93
+ file1_path = tmp_path / "file1.txt"
94
+ file2_path = tmp_path / "file2.txt"
95
+ file1_path.write_bytes(file1_content)
96
+ file2_path.write_bytes(file2_content)
97
+
98
+ async def handler(request: httpx.Request) -> httpx.Response:
99
+ assert request.method == "POST"
100
+ req_body = await request.aread()
101
+
102
+ # 验证两个文件的内容和文件名都被正确发送
103
+ assert file1_content in req_body
104
+ assert file2_content in req_body
105
+ assert b'filename="file1.txt"' in req_body
106
+ assert b'filename="file2.txt"' in req_body
107
+ assert b'name="file"' in req_body
108
+
109
+ return httpx.Response(
110
+ status_code=200,
111
+ json={"status": "uploaded_multiple"},
112
+ request=request,
113
+ )
114
+
115
+ transport = httpx.MockTransport(handler)
116
+
117
+ async with ByHttpClient(
118
+ base_url="https://example.com",
119
+ http_client=httpx.AsyncClient(
120
+ transport=transport,
121
+ base_url="https://example.com",
122
+ ),
123
+ retry_config=RetryConfig.no_retry(),
124
+ ) as client:
125
+ response = await client.upload_multiple(
126
+ "/upload",
127
+ [file1_path, file2_path],
128
+ file_field="file",
129
+ )
130
+
131
+ assert response.is_success is True
132
+ assert response.data == {"status": "uploaded_multiple"}
133
+
134
+
135
+ @pytest.mark.asyncio
136
+ async def test_upload_with_stream():
137
+ stream_content = b"stream upload data"
138
+ file_name = "stream_file.bin"
139
+
140
+ async def handler(request: httpx.Request) -> httpx.Response:
141
+ assert request.method == "POST"
142
+ req_body = await request.aread()
143
+
144
+ assert stream_content in req_body
145
+ assert f'filename="{file_name}"'.encode() in req_body
146
+ assert b'name="file"' in req_body
147
+
148
+ return httpx.Response(
149
+ status_code=200,
150
+ json={"status": "uploaded_stream"},
151
+ request=request,
152
+ )
153
+
154
+ transport = httpx.MockTransport(handler)
155
+
156
+ async with ByHttpClient(
157
+ base_url="https://example.com",
158
+ http_client=httpx.AsyncClient(
159
+ transport=transport,
160
+ base_url="https://example.com",
161
+ ),
162
+ retry_config=RetryConfig.no_retry(),
163
+ ) as client:
164
+ response = await client.upload_with_stream(
165
+ "/upload",
166
+ file_name,
167
+ stream_content,
168
+ content_type="application/octet-stream",
169
+ )
170
+
171
+ assert response.is_success is True
172
+ assert response.data == {"status": "uploaded_stream"}