by-framework 0.2.2.dev7__tar.gz → 0.2.2.dev8__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.
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/PKG-INFO +1 -1
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/pyproject.toml +1 -1
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/admin/cli.py +24 -8
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/config.py +13 -3
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/redis_client.py +2 -2
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/app.py +7 -4
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/admin/test_cli.py +112 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_config.py +47 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_redis_client.py +160 -1
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/uv.lock +1 -1
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.dockerignore +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/RELEASING.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/workflows/ci.yml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/workflows/publish.yml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/workflows/stale.yml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.gitignore +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.pre-commit-config.yaml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/AGENTS.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CHANGELOG.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CLAUDE.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CODE_OF_CONDUCT.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CONTRIBUTING.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/LICENSE +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/Makefile +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/README_zh.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/SECURITY.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/assets/img/architecture_en.png +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/assets/img/architecture_zh.png +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/autoformat.sh +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/by-framework-trace-change-usage.html +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/by-framework-trace-complete-guide.html +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/worker_management.html +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/tests/test_worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/Dockerfile +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/index.html +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/package.json +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/README.md +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/pyproject.toml +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/pylintrc +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/scripts/python_quality.sh +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/__main__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/admin/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/admin/worker_manager.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/client/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/client/byai_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/client/client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/constants.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/emitter.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/exceptions.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/logger.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/availability.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/delivery_gate.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/discovery.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/agent_config.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/plugin.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/registry.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/trace_provider.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/action_type.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/agent_state.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/byai_codec.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/byai_command.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/byai_types.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/commands.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/content_codec.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/content_type.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/data_message.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/data_shapes.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/event_type.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/events.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/message.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/message_header.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/responses.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/results.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/registry.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/file_manager.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/file_paths.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/file_permissions.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/filestore/base.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/filestore/local.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/base.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/history_manager.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/in_memory.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/session_manager.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/wakeup_controller.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/workspace.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/base.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/common.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/execution.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/http.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/protocol.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/registry.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/catalog.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/collector.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/read_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/snapshot.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/external_trace.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/span_recorder.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/trace_schema.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/trace_writer.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/generate_message_id.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/http_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/_control_handling.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/_execution_tracking.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/_message_processing.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/byai_context.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/byai_worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/context.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/heartbeat.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/processor.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/runner.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/sandbox/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/admin/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/client/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/client/test_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_constants.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_exceptions.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_logger.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/conftest.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_byai_codec.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_command_wire.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_protocol.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_results.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/history/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/history/test_history_persistence.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_access_context.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_paths.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_permissions.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_collection.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_read.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_search.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_agent_config_audit.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_availability.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_discovery.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_registry.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_worker_manager.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_ask_user_flow.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_callback_flow.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_logger_integration.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_scatter_gather.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_catalog.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_collector.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_metrics.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_read_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_snapshot.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_langfuse_plugin.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_discovery.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_improvements.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_registry.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_system.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_external_trace.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_logger_correlation.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_otel_exporter.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_span_recorder.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_trace_writer.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_discovery_http_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_discovery_http_client_download.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_discovery_http_client_upload.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_http_client.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/__init__.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_admin_control_handling.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_app.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_byai_worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_context.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_control_handling.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_emitter.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_fetch_messages.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_gateway_worker.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_heartbeat.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_message_processing.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_processor.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_runner.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_sandbox.py +0 -0
- {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_workspace.py +0 -0
|
@@ -13,7 +13,8 @@ from rich.console import Console
|
|
|
13
13
|
from rich.table import Table
|
|
14
14
|
|
|
15
15
|
from by_framework.admin.worker_manager import WorkerManager
|
|
16
|
-
from by_framework.common.
|
|
16
|
+
from by_framework.common.config import RedisConfig
|
|
17
|
+
from by_framework.common.redis_client import init_redis, init_redis_from_url
|
|
17
18
|
from by_framework.core.registry import WorkerRegistry
|
|
18
19
|
from by_framework.metrics.snapshot import (
|
|
19
20
|
build_observability_snapshot,
|
|
@@ -27,7 +28,12 @@ from by_framework.metrics.snapshot import (
|
|
|
27
28
|
app = typer.Typer(
|
|
28
29
|
name="by-admin",
|
|
29
30
|
no_args_is_help=True,
|
|
30
|
-
help=
|
|
31
|
+
help=(
|
|
32
|
+
"by-framework cluster admin CLI. For Redis Cluster, omit --redis-url "
|
|
33
|
+
"and set REDIS_CLUSTER_HOST (comma-separated host:port list; implies "
|
|
34
|
+
"cluster mode) and REDIS_KEY_SCHEMA_VERSION=v2. REDIS_MODE=cluster + "
|
|
35
|
+
"REDIS_CLUSTER_NODES also still works."
|
|
36
|
+
),
|
|
31
37
|
)
|
|
32
38
|
worker_app = typer.Typer(no_args_is_help=True)
|
|
33
39
|
type_app = typer.Typer(no_args_is_help=True)
|
|
@@ -43,7 +49,8 @@ app.add_typer(metrics_app, name="metrics", help="Metrics and observability")
|
|
|
43
49
|
|
|
44
50
|
console = Console()
|
|
45
51
|
err_console = Console(stderr=True)
|
|
46
|
-
|
|
52
|
+
_DEFAULT_REDIS_URL = "redis://localhost:6379/0"
|
|
53
|
+
_redis_url: Optional[str] = None
|
|
47
54
|
|
|
48
55
|
|
|
49
56
|
@app.callback()
|
|
@@ -52,13 +59,16 @@ def _global(
|
|
|
52
59
|
None,
|
|
53
60
|
"--redis-url",
|
|
54
61
|
envvar=["BYAI_REDIS_URL", "REDIS_URL"],
|
|
55
|
-
help=
|
|
62
|
+
help=(
|
|
63
|
+
"Standalone Redis connection URL [env: BYAI_REDIS_URL]. "
|
|
64
|
+
"For Redis Cluster, omit this and set REDIS_CLUSTER_HOST "
|
|
65
|
+
"(comma-separated host:port list)."
|
|
66
|
+
),
|
|
56
67
|
),
|
|
57
68
|
):
|
|
58
69
|
"""by-framework cluster admin CLI."""
|
|
59
70
|
global _redis_url
|
|
60
|
-
|
|
61
|
-
_redis_url = redis_url
|
|
71
|
+
_redis_url = redis_url
|
|
62
72
|
|
|
63
73
|
|
|
64
74
|
def _run(coro): # type: ignore[no-untyped-def]
|
|
@@ -70,8 +80,14 @@ def _die(msg: str, code: int = 1) -> None:
|
|
|
70
80
|
raise typer.Exit(code)
|
|
71
81
|
|
|
72
82
|
|
|
73
|
-
def _get_redis():
|
|
74
|
-
|
|
83
|
+
def _get_redis(redis_url: Optional[str] = None):
|
|
84
|
+
configured_url = redis_url if redis_url is not None else _redis_url
|
|
85
|
+
if configured_url:
|
|
86
|
+
return init_redis_from_url(configured_url)
|
|
87
|
+
config = RedisConfig.from_env()
|
|
88
|
+
if config.mode == "cluster":
|
|
89
|
+
return init_redis(config=config)
|
|
90
|
+
return init_redis_from_url(_DEFAULT_REDIS_URL)
|
|
75
91
|
|
|
76
92
|
|
|
77
93
|
# --------------------------------------------------------------------------- #
|
|
@@ -29,12 +29,22 @@ class RedisConfig:
|
|
|
29
29
|
|
|
30
30
|
@classmethod
|
|
31
31
|
def from_env(cls) -> "RedisConfig":
|
|
32
|
-
"""Load Redis configuration from environment variables.
|
|
32
|
+
"""Load Redis configuration from environment variables.
|
|
33
|
+
|
|
34
|
+
REDIS_CLUSTER_HOST (comma-separated "host:port" list) is the
|
|
35
|
+
preferred way to configure Cluster mode: just setting it is enough
|
|
36
|
+
to switch to cluster mode, no separate REDIS_MODE=cluster needed.
|
|
37
|
+
An explicit REDIS_MODE still takes precedence when set, so existing
|
|
38
|
+
explicit REDIS_MODE=cluster + REDIS_CLUSTER_NODES setups keep working.
|
|
39
|
+
"""
|
|
33
40
|
password = os.environ.get("REDIS_PASSWORD", "")
|
|
34
41
|
username = os.environ.get("REDIS_USERNAME") or None
|
|
35
42
|
max_connections = os.environ.get("REDIS_MAX_CONNECTIONS")
|
|
36
|
-
|
|
37
|
-
cluster_nodes_str = os.environ.get("REDIS_CLUSTER_NODES")
|
|
43
|
+
cluster_host_str = os.environ.get("REDIS_CLUSTER_HOST")
|
|
44
|
+
cluster_nodes_str = cluster_host_str or os.environ.get("REDIS_CLUSTER_NODES")
|
|
45
|
+
mode = os.environ.get("REDIS_MODE") or (
|
|
46
|
+
"cluster" if cluster_host_str else "standalone"
|
|
47
|
+
)
|
|
38
48
|
cluster_nodes = None
|
|
39
49
|
if cluster_nodes_str:
|
|
40
50
|
cluster_nodes = []
|
|
@@ -112,11 +112,11 @@ def init_redis_from_url(url: str) -> Redis:
|
|
|
112
112
|
def get_redis() -> Redis:
|
|
113
113
|
"""Get the initialized global Redis client.
|
|
114
114
|
|
|
115
|
-
If not initialized,
|
|
115
|
+
If not initialized, automatically initialize from environment config.
|
|
116
116
|
"""
|
|
117
117
|
global _redis_client # pylint: disable=global-variable-not-assigned
|
|
118
118
|
if _redis_client is None:
|
|
119
|
-
return init_redis()
|
|
119
|
+
return init_redis(config=RedisConfig.from_env())
|
|
120
120
|
return _redis_client
|
|
121
121
|
|
|
122
122
|
|
|
@@ -276,11 +276,14 @@ def run_worker(
|
|
|
276
276
|
Redis configuration: every redis_* argument defaults to None, meaning
|
|
277
277
|
"not specified" - when not passed, the value is read from the
|
|
278
278
|
corresponding REDIS_* environment variable (REDIS_HOST, REDIS_PORT,
|
|
279
|
-
REDIS_DB, REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE,
|
|
279
|
+
REDIS_DB, REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE, REDIS_CLUSTER_HOST,
|
|
280
280
|
REDIS_CLUSTER_NODES), falling back to "localhost"/6379/standalone if
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
281
|
+
none is set. Setting REDIS_CLUSTER_HOST (comma-separated "host:port"
|
|
282
|
+
list) alone is enough to switch to cluster mode - no separate
|
|
283
|
+
REDIS_MODE=cluster required, though an explicit REDIS_MODE still wins if
|
|
284
|
+
set. An explicitly-passed argument always takes precedence over its env
|
|
285
|
+
var, for every field, in both standalone and cluster mode - so either
|
|
286
|
+
mode can be configured purely programmatically (e.g.
|
|
284
287
|
``redis_mode="cluster", redis_cluster_nodes=[("h1", 6379)]``) or purely
|
|
285
288
|
via env vars, without the two modes behaving inconsistently.
|
|
286
289
|
|
|
@@ -12,7 +12,9 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
12
12
|
|
|
13
13
|
from typer.testing import CliRunner
|
|
14
14
|
|
|
15
|
+
from by_framework.admin import cli
|
|
15
16
|
from by_framework.admin.cli import app
|
|
17
|
+
from by_framework.common.config import RedisConfig
|
|
16
18
|
|
|
17
19
|
runner = CliRunner()
|
|
18
20
|
|
|
@@ -43,6 +45,17 @@ def _mock_redis():
|
|
|
43
45
|
return MagicMock()
|
|
44
46
|
|
|
45
47
|
|
|
48
|
+
def _cluster_env(monkeypatch):
|
|
49
|
+
cli._redis_url = None # pylint: disable=protected-access
|
|
50
|
+
monkeypatch.setenv("REDIS_MODE", "cluster")
|
|
51
|
+
monkeypatch.setenv("REDIS_CLUSTER_NODES", "h1:6379,h2:6380")
|
|
52
|
+
monkeypatch.setenv("REDIS_USERNAME", "cluster-user")
|
|
53
|
+
monkeypatch.setenv("REDIS_PASSWORD", "cluster-secret")
|
|
54
|
+
monkeypatch.setenv("REDIS_KEY_SCHEMA_VERSION", "v2")
|
|
55
|
+
monkeypatch.delenv("BYAI_REDIS_URL", raising=False)
|
|
56
|
+
monkeypatch.delenv("REDIS_URL", raising=False)
|
|
57
|
+
|
|
58
|
+
|
|
46
59
|
# --------------------------------------------------------------------------- #
|
|
47
60
|
# worker list
|
|
48
61
|
# --------------------------------------------------------------------------- #
|
|
@@ -373,3 +386,102 @@ def test_redis_url_env_var(mock_init, mock_registry_cls, monkeypatch):
|
|
|
373
386
|
|
|
374
387
|
runner.invoke(app, ["worker", "list"])
|
|
375
388
|
mock_init.assert_called_with("redis://envhost:6379/2")
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def test_help_mentions_cluster_env_configuration():
|
|
392
|
+
result = runner.invoke(app, ["--help"])
|
|
393
|
+
|
|
394
|
+
assert result.exit_code == 0
|
|
395
|
+
assert "REDIS_MODE=cluster" in result.output
|
|
396
|
+
assert "REDIS_CLUSTER_NODES" in result.output
|
|
397
|
+
assert "REDIS_KEY_SCHEMA_VERSION=v2" in result.output
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
|
|
401
|
+
@patch("by_framework.admin.cli.init_redis_from_url", return_value=_mock_redis())
|
|
402
|
+
def test_get_redis_uses_cluster_config_from_env(
|
|
403
|
+
mock_init_from_url, mock_init, monkeypatch
|
|
404
|
+
):
|
|
405
|
+
_cluster_env(monkeypatch)
|
|
406
|
+
|
|
407
|
+
redis = cli._get_redis() # pylint: disable=protected-access
|
|
408
|
+
|
|
409
|
+
assert redis is mock_init.return_value
|
|
410
|
+
mock_init_from_url.assert_not_called()
|
|
411
|
+
mock_init.assert_called_once()
|
|
412
|
+
config = mock_init.call_args.kwargs["config"]
|
|
413
|
+
assert isinstance(config, RedisConfig)
|
|
414
|
+
assert config.mode == "cluster"
|
|
415
|
+
assert config.cluster_nodes == [("h1", 6379), ("h2", 6380)]
|
|
416
|
+
assert config.username == "cluster-user"
|
|
417
|
+
assert config.password == "cluster-secret"
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
|
|
421
|
+
@patch("by_framework.admin.cli.init_redis_from_url", return_value=_mock_redis())
|
|
422
|
+
def test_get_redis_prefers_explicit_url_over_cluster_env(
|
|
423
|
+
mock_init_from_url, mock_init, monkeypatch
|
|
424
|
+
):
|
|
425
|
+
_cluster_env(monkeypatch)
|
|
426
|
+
|
|
427
|
+
redis = cli._get_redis("redis://operator-host:6379/0") # pylint: disable=protected-access
|
|
428
|
+
|
|
429
|
+
assert redis is mock_init_from_url.return_value
|
|
430
|
+
mock_init_from_url.assert_called_once_with("redis://operator-host:6379/0")
|
|
431
|
+
mock_init.assert_not_called()
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@patch("by_framework.admin.cli.init_redis", side_effect=RuntimeError("v2 required"))
|
|
435
|
+
def test_get_redis_cluster_mode_surfaces_schema_error(mock_init, monkeypatch):
|
|
436
|
+
monkeypatch.setenv("REDIS_MODE", "cluster")
|
|
437
|
+
monkeypatch.setenv("REDIS_CLUSTER_NODES", "h1:6379,h2:6380")
|
|
438
|
+
monkeypatch.setenv("REDIS_KEY_SCHEMA_VERSION", "v1")
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
cli._get_redis() # pylint: disable=protected-access
|
|
442
|
+
except RuntimeError as err:
|
|
443
|
+
assert "v2 required" in str(err)
|
|
444
|
+
else:
|
|
445
|
+
raise AssertionError("cluster schema error was not surfaced")
|
|
446
|
+
|
|
447
|
+
mock_init.assert_called_once()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@patch("by_framework.admin.cli.WorkerRegistry")
|
|
451
|
+
@patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
|
|
452
|
+
def test_worker_list_uses_cluster_config(mock_init, mock_registry_cls, monkeypatch):
|
|
453
|
+
_cluster_env(monkeypatch)
|
|
454
|
+
mock_registry = MagicMock()
|
|
455
|
+
mock_registry.get_all_workers = AsyncMock(return_value={})
|
|
456
|
+
mock_registry_cls.return_value = mock_registry
|
|
457
|
+
|
|
458
|
+
result = runner.invoke(app, ["worker", "list"])
|
|
459
|
+
|
|
460
|
+
assert result.exit_code == 0
|
|
461
|
+
assert mock_init.call_args.kwargs["config"].mode == "cluster"
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@patch("by_framework.admin.cli.WorkerManager")
|
|
465
|
+
@patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
|
|
466
|
+
def test_type_deny_uses_cluster_config(mock_init, mock_mgr_cls, monkeypatch):
|
|
467
|
+
_cluster_env(monkeypatch)
|
|
468
|
+
mock_mgr = MagicMock()
|
|
469
|
+
mock_mgr.deny_worker_for_type = AsyncMock()
|
|
470
|
+
mock_mgr_cls.return_value = mock_mgr
|
|
471
|
+
|
|
472
|
+
result = runner.invoke(app, ["type", "deny", "chat", "worker-a"])
|
|
473
|
+
|
|
474
|
+
assert result.exit_code == 0
|
|
475
|
+
assert mock_init.call_args.kwargs["config"].mode == "cluster"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@patch("by_framework.admin.cli.build_observability_snapshot", new_callable=AsyncMock)
|
|
479
|
+
@patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
|
|
480
|
+
def test_metrics_snapshot_uses_cluster_config(mock_init, mock_snapshot, monkeypatch):
|
|
481
|
+
_cluster_env(monkeypatch)
|
|
482
|
+
mock_snapshot.return_value = _SNAPSHOT
|
|
483
|
+
|
|
484
|
+
result = runner.invoke(app, ["metrics", "snapshot"])
|
|
485
|
+
|
|
486
|
+
assert result.exit_code == 0
|
|
487
|
+
assert mock_init.call_args.kwargs["config"].mode == "cluster"
|
|
@@ -101,6 +101,53 @@ class TestRedisConfig(unittest.TestCase):
|
|
|
101
101
|
else:
|
|
102
102
|
os.environ.pop("REDIS_CLUSTER_NODES", None)
|
|
103
103
|
|
|
104
|
+
def test_from_env_cluster_host_implies_cluster_mode(self):
|
|
105
|
+
"""Setting REDIS_CLUSTER_HOST alone (no REDIS_MODE) switches to cluster mode."""
|
|
106
|
+
env_vars = ["REDIS_MODE", "REDIS_CLUSTER_HOST", "REDIS_CLUSTER_NODES"]
|
|
107
|
+
old_values = {k: os.environ.get(k) for k in env_vars}
|
|
108
|
+
try:
|
|
109
|
+
for k in env_vars:
|
|
110
|
+
os.environ.pop(k, None)
|
|
111
|
+
|
|
112
|
+
config = RedisConfig.from_env()
|
|
113
|
+
self.assertEqual(config.mode, "standalone")
|
|
114
|
+
self.assertIsNone(config.cluster_nodes)
|
|
115
|
+
|
|
116
|
+
os.environ["REDIS_CLUSTER_HOST"] = (
|
|
117
|
+
"10.10.168.203:6371,10.10.168.203:6372,10.10.168.203:6373"
|
|
118
|
+
)
|
|
119
|
+
config = RedisConfig.from_env()
|
|
120
|
+
self.assertEqual(config.mode, "cluster")
|
|
121
|
+
self.assertEqual(
|
|
122
|
+
config.cluster_nodes,
|
|
123
|
+
[
|
|
124
|
+
("10.10.168.203", 6371),
|
|
125
|
+
("10.10.168.203", 6372),
|
|
126
|
+
("10.10.168.203", 6373),
|
|
127
|
+
],
|
|
128
|
+
)
|
|
129
|
+
finally:
|
|
130
|
+
for k, v in old_values.items():
|
|
131
|
+
if v is not None:
|
|
132
|
+
os.environ[k] = v
|
|
133
|
+
else:
|
|
134
|
+
os.environ.pop(k, None)
|
|
135
|
+
|
|
136
|
+
def test_from_env_explicit_redis_mode_overrides_cluster_host(self):
|
|
137
|
+
"""An explicit REDIS_MODE still wins over REDIS_CLUSTER_HOST's implied mode."""
|
|
138
|
+
env_vars = ["REDIS_MODE", "REDIS_CLUSTER_HOST"]
|
|
139
|
+
old_values = {k: os.environ.get(k) for k in env_vars}
|
|
140
|
+
try:
|
|
141
|
+
os.environ["REDIS_MODE"] = "standalone"
|
|
142
|
+
os.environ["REDIS_CLUSTER_HOST"] = "h1:6379"
|
|
143
|
+
self.assertEqual(RedisConfig.from_env().mode, "standalone")
|
|
144
|
+
finally:
|
|
145
|
+
for k, v in old_values.items():
|
|
146
|
+
if v is not None:
|
|
147
|
+
os.environ[k] = v
|
|
148
|
+
else:
|
|
149
|
+
os.environ.pop(k, None)
|
|
150
|
+
|
|
104
151
|
def test_from_env_with_custom_values(self):
|
|
105
152
|
"""Test from_env with custom environment variables."""
|
|
106
153
|
old_values = {
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import unittest
|
|
3
|
-
from unittest.mock import patch
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
4
|
|
|
5
|
+
from by_framework.client.client import GatewayClient
|
|
5
6
|
from by_framework.common.config import RedisConfig
|
|
6
7
|
from by_framework.common.exceptions import RedisConnectionError
|
|
7
8
|
from by_framework.common.redis_client import close_redis, get_redis, init_redis
|
|
9
|
+
from by_framework.core.discovery import DiscoveryClient, ServiceRegistry
|
|
10
|
+
from by_framework.core.registry import WorkerRegistry
|
|
11
|
+
|
|
12
|
+
REDIS_ENV_VARS = (
|
|
13
|
+
"REDIS_HOST",
|
|
14
|
+
"REDIS_PORT",
|
|
15
|
+
"REDIS_DB",
|
|
16
|
+
"REDIS_PASSWORD",
|
|
17
|
+
"REDIS_USERNAME",
|
|
18
|
+
"REDIS_MAX_CONNECTIONS",
|
|
19
|
+
"REDIS_MODE",
|
|
20
|
+
"REDIS_CLUSTER_NODES",
|
|
21
|
+
"REDIS_CLUSTER_HOST",
|
|
22
|
+
"REDIS_KEY_SCHEMA_VERSION",
|
|
23
|
+
)
|
|
8
24
|
|
|
9
25
|
|
|
10
26
|
class TestRedisClient(unittest.IsolatedAsyncioTestCase):
|
|
@@ -135,6 +151,149 @@ class TestRedisClient(unittest.IsolatedAsyncioTestCase):
|
|
|
135
151
|
self.assertEqual(mock_redis_cls.call_count, 1)
|
|
136
152
|
self.assertIsNotNone(client)
|
|
137
153
|
|
|
154
|
+
async def test_get_redis_uses_standalone_defaults_without_env(self):
|
|
155
|
+
"""The default singleton path remains localhost standalone compatible."""
|
|
156
|
+
with patch.dict(os.environ, {}, clear=False):
|
|
157
|
+
for name in REDIS_ENV_VARS:
|
|
158
|
+
os.environ.pop(name, None)
|
|
159
|
+
|
|
160
|
+
with patch("by_framework.common.redis_client.Redis") as mock_redis_cls:
|
|
161
|
+
get_redis()
|
|
162
|
+
|
|
163
|
+
_, kwargs = mock_redis_cls.call_args
|
|
164
|
+
self.assertEqual(kwargs["host"], "localhost")
|
|
165
|
+
self.assertEqual(kwargs["port"], 6379)
|
|
166
|
+
self.assertEqual(kwargs["db"], 0)
|
|
167
|
+
self.assertTrue(kwargs["decode_responses"])
|
|
168
|
+
|
|
169
|
+
async def test_get_redis_uses_cluster_config_from_env(self):
|
|
170
|
+
"""The shared default lookup honors REDIS_MODE=cluster env config."""
|
|
171
|
+
with patch.dict(
|
|
172
|
+
os.environ,
|
|
173
|
+
{
|
|
174
|
+
"REDIS_MODE": "cluster",
|
|
175
|
+
"REDIS_CLUSTER_NODES": "h1:6379,h2:6380",
|
|
176
|
+
"REDIS_USERNAME": "cluster-user",
|
|
177
|
+
"REDIS_PASSWORD": "cluster-secret",
|
|
178
|
+
"REDIS_KEY_SCHEMA_VERSION": "v2",
|
|
179
|
+
},
|
|
180
|
+
clear=False,
|
|
181
|
+
):
|
|
182
|
+
with (
|
|
183
|
+
patch("by_framework.common.redis_client.Redis") as mock_redis_cls,
|
|
184
|
+
patch(
|
|
185
|
+
"by_framework.common.redis_client.RedisCluster"
|
|
186
|
+
) as mock_cluster_cls,
|
|
187
|
+
):
|
|
188
|
+
get_redis()
|
|
189
|
+
|
|
190
|
+
mock_redis_cls.assert_not_called()
|
|
191
|
+
mock_cluster_cls.assert_called_once()
|
|
192
|
+
_, kwargs = mock_cluster_cls.call_args
|
|
193
|
+
self.assertEqual(
|
|
194
|
+
[(n.host, n.port) for n in kwargs["startup_nodes"]],
|
|
195
|
+
[("h1", 6379), ("h2", 6380)],
|
|
196
|
+
)
|
|
197
|
+
self.assertEqual(kwargs["username"], "cluster-user")
|
|
198
|
+
self.assertEqual(kwargs["password"], "cluster-secret")
|
|
199
|
+
self.assertTrue(kwargs["decode_responses"])
|
|
200
|
+
|
|
201
|
+
async def test_get_redis_uses_cluster_host_env_without_explicit_mode(self):
|
|
202
|
+
"""Setting only REDIS_CLUSTER_HOST (no REDIS_MODE) is enough to
|
|
203
|
+
switch the shared default lookup to cluster mode."""
|
|
204
|
+
with patch.dict(
|
|
205
|
+
os.environ,
|
|
206
|
+
{
|
|
207
|
+
"REDIS_CLUSTER_HOST": "10.10.168.203:6371,10.10.168.203:6372",
|
|
208
|
+
"REDIS_KEY_SCHEMA_VERSION": "v2",
|
|
209
|
+
},
|
|
210
|
+
clear=False,
|
|
211
|
+
):
|
|
212
|
+
os.environ.pop("REDIS_MODE", None)
|
|
213
|
+
with (
|
|
214
|
+
patch("by_framework.common.redis_client.Redis") as mock_redis_cls,
|
|
215
|
+
patch(
|
|
216
|
+
"by_framework.common.redis_client.RedisCluster"
|
|
217
|
+
) as mock_cluster_cls,
|
|
218
|
+
):
|
|
219
|
+
get_redis()
|
|
220
|
+
|
|
221
|
+
mock_redis_cls.assert_not_called()
|
|
222
|
+
mock_cluster_cls.assert_called_once()
|
|
223
|
+
_, kwargs = mock_cluster_cls.call_args
|
|
224
|
+
self.assertEqual(
|
|
225
|
+
[(n.host, n.port) for n in kwargs["startup_nodes"]],
|
|
226
|
+
[("10.10.168.203", 6371), ("10.10.168.203", 6372)],
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def test_get_redis_cluster_env_requires_v2_schema(self):
|
|
230
|
+
"""Cluster env config fails fast before constructing a client."""
|
|
231
|
+
with patch.dict(
|
|
232
|
+
os.environ,
|
|
233
|
+
{
|
|
234
|
+
"REDIS_MODE": "cluster",
|
|
235
|
+
"REDIS_CLUSTER_NODES": "h1:6379,h2:6380",
|
|
236
|
+
},
|
|
237
|
+
clear=False,
|
|
238
|
+
):
|
|
239
|
+
os.environ.pop("REDIS_KEY_SCHEMA_VERSION", None)
|
|
240
|
+
with (
|
|
241
|
+
patch("by_framework.common.redis_client.Redis") as mock_redis_cls,
|
|
242
|
+
patch(
|
|
243
|
+
"by_framework.common.redis_client.RedisCluster"
|
|
244
|
+
) as mock_cluster_cls,
|
|
245
|
+
):
|
|
246
|
+
with self.assertRaises(RedisConnectionError):
|
|
247
|
+
get_redis()
|
|
248
|
+
|
|
249
|
+
mock_redis_cls.assert_not_called()
|
|
250
|
+
mock_cluster_cls.assert_not_called()
|
|
251
|
+
|
|
252
|
+
async def test_default_component_construction_uses_cluster_env(self):
|
|
253
|
+
"""Public constructors using the shared default get a cluster client."""
|
|
254
|
+
with patch.dict(
|
|
255
|
+
os.environ,
|
|
256
|
+
{
|
|
257
|
+
"REDIS_MODE": "cluster",
|
|
258
|
+
"REDIS_CLUSTER_NODES": "h1:6379,h2:6380",
|
|
259
|
+
"REDIS_KEY_SCHEMA_VERSION": "v2",
|
|
260
|
+
},
|
|
261
|
+
clear=False,
|
|
262
|
+
):
|
|
263
|
+
with patch(
|
|
264
|
+
"by_framework.common.redis_client.RedisCluster"
|
|
265
|
+
) as mock_cluster_cls:
|
|
266
|
+
service_registry = ServiceRegistry()
|
|
267
|
+
worker_registry = WorkerRegistry()
|
|
268
|
+
gateway_client = GatewayClient()
|
|
269
|
+
|
|
270
|
+
mock_cluster_cls.assert_called_once()
|
|
271
|
+
self.assertIs(service_registry.redis, mock_cluster_cls.return_value)
|
|
272
|
+
self.assertIs(worker_registry.redis, mock_cluster_cls.return_value)
|
|
273
|
+
self.assertIs(gateway_client.redis, mock_cluster_cls.return_value)
|
|
274
|
+
|
|
275
|
+
async def test_explicit_redis_clients_take_precedence(self):
|
|
276
|
+
"""Injected Redis clients are not replaced by the shared default lookup."""
|
|
277
|
+
redis = MagicMock()
|
|
278
|
+
|
|
279
|
+
with (
|
|
280
|
+
patch("by_framework.core.discovery.get_redis") as discovery_get_redis,
|
|
281
|
+
patch("by_framework.core.registry.get_redis") as registry_get_redis,
|
|
282
|
+
patch("by_framework.client.client.get_redis") as client_get_redis,
|
|
283
|
+
):
|
|
284
|
+
service_registry = ServiceRegistry(redis_client=redis)
|
|
285
|
+
discovery_client = DiscoveryClient(redis_client=redis)
|
|
286
|
+
worker_registry = WorkerRegistry(redis_client=redis)
|
|
287
|
+
gateway_client = GatewayClient(redis_client=redis)
|
|
288
|
+
|
|
289
|
+
discovery_get_redis.assert_not_called()
|
|
290
|
+
registry_get_redis.assert_not_called()
|
|
291
|
+
client_get_redis.assert_not_called()
|
|
292
|
+
self.assertIs(service_registry.redis, redis)
|
|
293
|
+
self.assertIs(discovery_client.redis, redis)
|
|
294
|
+
self.assertIs(worker_registry.redis, redis)
|
|
295
|
+
self.assertIs(gateway_client.redis, redis)
|
|
296
|
+
|
|
138
297
|
async def test_close_redis(self):
|
|
139
298
|
"""Verify that close_redis closes the connection and clears the singleton."""
|
|
140
299
|
with patch("by_framework.common.redis_client.Redis") as mock_redis_cls:
|
|
File without changes
|
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/by-framework-trace-change-usage.html
RENAMED
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/by-framework-trace-complete-guide.html
RENAMED
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/plans/2026-05-15-worker-task-state-stats.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/tests/test_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/index.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/pyproject.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|