jaf-py 2.5.10__py3-none-any.whl → 2.5.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- jaf/__init__.py +154 -57
- jaf/a2a/__init__.py +42 -21
- jaf/a2a/agent.py +79 -126
- jaf/a2a/agent_card.py +87 -78
- jaf/a2a/client.py +30 -66
- jaf/a2a/examples/client_example.py +12 -12
- jaf/a2a/examples/integration_example.py +38 -47
- jaf/a2a/examples/server_example.py +56 -53
- jaf/a2a/memory/__init__.py +0 -4
- jaf/a2a/memory/cleanup.py +28 -21
- jaf/a2a/memory/factory.py +155 -133
- jaf/a2a/memory/providers/composite.py +21 -26
- jaf/a2a/memory/providers/in_memory.py +89 -83
- jaf/a2a/memory/providers/postgres.py +117 -115
- jaf/a2a/memory/providers/redis.py +128 -121
- jaf/a2a/memory/serialization.py +77 -87
- jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
- jaf/a2a/memory/tests/test_cleanup.py +211 -94
- jaf/a2a/memory/tests/test_serialization.py +73 -68
- jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
- jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
- jaf/a2a/memory/types.py +91 -53
- jaf/a2a/protocol.py +95 -125
- jaf/a2a/server.py +90 -118
- jaf/a2a/standalone_client.py +30 -43
- jaf/a2a/tests/__init__.py +16 -33
- jaf/a2a/tests/run_tests.py +17 -53
- jaf/a2a/tests/test_agent.py +40 -140
- jaf/a2a/tests/test_client.py +54 -117
- jaf/a2a/tests/test_integration.py +28 -82
- jaf/a2a/tests/test_protocol.py +54 -139
- jaf/a2a/tests/test_types.py +50 -136
- jaf/a2a/types.py +58 -34
- jaf/cli.py +21 -41
- jaf/core/__init__.py +7 -1
- jaf/core/agent_tool.py +93 -72
- jaf/core/analytics.py +257 -207
- jaf/core/checkpoint.py +223 -0
- jaf/core/composition.py +249 -235
- jaf/core/engine.py +817 -519
- jaf/core/errors.py +55 -42
- jaf/core/guardrails.py +276 -202
- jaf/core/handoff.py +47 -31
- jaf/core/parallel_agents.py +69 -75
- jaf/core/performance.py +75 -73
- jaf/core/proxy.py +43 -44
- jaf/core/proxy_helpers.py +24 -27
- jaf/core/regeneration.py +220 -129
- jaf/core/state.py +68 -66
- jaf/core/streaming.py +115 -108
- jaf/core/tool_results.py +111 -101
- jaf/core/tools.py +114 -116
- jaf/core/tracing.py +310 -210
- jaf/core/types.py +403 -151
- jaf/core/workflows.py +209 -168
- jaf/exceptions.py +46 -38
- jaf/memory/__init__.py +1 -6
- jaf/memory/approval_storage.py +54 -77
- jaf/memory/factory.py +4 -4
- jaf/memory/providers/in_memory.py +216 -180
- jaf/memory/providers/postgres.py +216 -146
- jaf/memory/providers/redis.py +173 -116
- jaf/memory/types.py +70 -51
- jaf/memory/utils.py +36 -34
- jaf/plugins/__init__.py +12 -12
- jaf/plugins/base.py +105 -96
- jaf/policies/__init__.py +0 -1
- jaf/policies/handoff.py +37 -46
- jaf/policies/validation.py +76 -52
- jaf/providers/__init__.py +6 -3
- jaf/providers/mcp.py +97 -51
- jaf/providers/model.py +475 -283
- jaf/server/__init__.py +1 -1
- jaf/server/main.py +7 -11
- jaf/server/server.py +514 -359
- jaf/server/types.py +208 -52
- jaf/utils/__init__.py +17 -18
- jaf/utils/attachments.py +111 -116
- jaf/utils/document_processor.py +175 -174
- jaf/visualization/__init__.py +1 -1
- jaf/visualization/example.py +111 -110
- jaf/visualization/functional_core.py +46 -71
- jaf/visualization/graphviz.py +154 -189
- jaf/visualization/imperative_shell.py +7 -16
- jaf/visualization/types.py +8 -4
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/METADATA +2 -2
- jaf_py-2.5.12.dist-info/RECORD +97 -0
- jaf_py-2.5.10.dist-info/RECORD +0 -96
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
jaf/a2a/standalone_client.py
CHANGED
|
@@ -17,6 +17,7 @@ from pydantic import BaseModel, Field
|
|
|
17
17
|
# Standalone types for the client
|
|
18
18
|
class StandaloneA2AClientConfig(BaseModel):
|
|
19
19
|
"""A2A client configuration"""
|
|
20
|
+
|
|
20
21
|
model_config = {"frozen": True}
|
|
21
22
|
|
|
22
23
|
base_url: str = Field(alias="baseUrl")
|
|
@@ -25,6 +26,7 @@ class StandaloneA2AClientConfig(BaseModel):
|
|
|
25
26
|
|
|
26
27
|
class StandaloneA2AClientState(BaseModel):
|
|
27
28
|
"""A2A client state"""
|
|
29
|
+
|
|
28
30
|
model_config = {"frozen": True}
|
|
29
31
|
|
|
30
32
|
config: StandaloneA2AClientConfig
|
|
@@ -32,23 +34,23 @@ class StandaloneA2AClientState(BaseModel):
|
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
# Client functions
|
|
35
|
-
def create_standalone_a2a_client(
|
|
37
|
+
def create_standalone_a2a_client(
|
|
38
|
+
base_url: str, config: Optional[Dict[str, Any]] = None
|
|
39
|
+
) -> StandaloneA2AClientState:
|
|
36
40
|
"""Pure function to create standalone A2A client"""
|
|
37
41
|
config = config or {}
|
|
38
42
|
|
|
39
43
|
return StandaloneA2AClientState(
|
|
40
44
|
config=StandaloneA2AClientConfig(
|
|
41
45
|
baseUrl=base_url.rstrip("/"), # Remove trailing slash
|
|
42
|
-
timeout=config.get("timeout", 30000)
|
|
46
|
+
timeout=config.get("timeout", 30000),
|
|
43
47
|
),
|
|
44
|
-
sessionId=f"client_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}"
|
|
48
|
+
sessionId=f"client_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
|
|
45
49
|
)
|
|
46
50
|
|
|
47
51
|
|
|
48
52
|
def create_standalone_message_request(
|
|
49
|
-
message: str,
|
|
50
|
-
session_id: str,
|
|
51
|
-
configuration: Optional[Dict[str, Any]] = None
|
|
53
|
+
message: str, session_id: str, configuration: Optional[Dict[str, Any]] = None
|
|
52
54
|
) -> Dict[str, Any]:
|
|
53
55
|
"""Pure function to create message request"""
|
|
54
56
|
return {
|
|
@@ -61,17 +63,15 @@ def create_standalone_message_request(
|
|
|
61
63
|
"parts": [{"kind": "text", "text": message}],
|
|
62
64
|
"messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
|
|
63
65
|
"contextId": session_id,
|
|
64
|
-
"kind": "message"
|
|
66
|
+
"kind": "message",
|
|
65
67
|
},
|
|
66
|
-
"configuration": configuration
|
|
67
|
-
}
|
|
68
|
+
"configuration": configuration,
|
|
69
|
+
},
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
def create_standalone_streaming_message_request(
|
|
72
|
-
message: str,
|
|
73
|
-
session_id: str,
|
|
74
|
-
configuration: Optional[Dict[str, Any]] = None
|
|
74
|
+
message: str, session_id: str, configuration: Optional[Dict[str, Any]] = None
|
|
75
75
|
) -> Dict[str, Any]:
|
|
76
76
|
"""Pure function to create streaming message request"""
|
|
77
77
|
return {
|
|
@@ -84,17 +84,15 @@ def create_standalone_streaming_message_request(
|
|
|
84
84
|
"parts": [{"kind": "text", "text": message}],
|
|
85
85
|
"messageId": f"msg_{int(time.time() * 1000)}_{uuid.uuid4().hex[:8]}",
|
|
86
86
|
"contextId": session_id,
|
|
87
|
-
"kind": "message"
|
|
87
|
+
"kind": "message",
|
|
88
88
|
},
|
|
89
|
-
"configuration": configuration
|
|
90
|
-
}
|
|
89
|
+
"configuration": configuration,
|
|
90
|
+
},
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
async def send_standalone_http_request(
|
|
95
|
-
url: str,
|
|
96
|
-
body: Dict[str, Any],
|
|
97
|
-
timeout: int = 30000
|
|
95
|
+
url: str, body: Dict[str, Any], timeout: int = 30000
|
|
98
96
|
) -> Dict[str, Any]:
|
|
99
97
|
"""Pure function to send HTTP request"""
|
|
100
98
|
timeout_seconds = timeout / 1000.0
|
|
@@ -104,10 +102,7 @@ async def send_standalone_http_request(
|
|
|
104
102
|
response = await client.post(
|
|
105
103
|
url,
|
|
106
104
|
json=body,
|
|
107
|
-
headers={
|
|
108
|
-
"Content-Type": "application/json",
|
|
109
|
-
"Accept": "application/json"
|
|
110
|
-
}
|
|
105
|
+
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
111
106
|
)
|
|
112
107
|
|
|
113
108
|
if not response.is_success:
|
|
@@ -120,8 +115,7 @@ async def send_standalone_http_request(
|
|
|
120
115
|
|
|
121
116
|
|
|
122
117
|
async def send_standalone_a2a_request(
|
|
123
|
-
client: StandaloneA2AClientState,
|
|
124
|
-
request: Dict[str, Any]
|
|
118
|
+
client: StandaloneA2AClientState, request: Dict[str, Any]
|
|
125
119
|
) -> Dict[str, Any]:
|
|
126
120
|
"""Pure function to send A2A request"""
|
|
127
121
|
url = f"{client.config.base_url}/a2a"
|
|
@@ -129,9 +123,7 @@ async def send_standalone_a2a_request(
|
|
|
129
123
|
|
|
130
124
|
|
|
131
125
|
async def send_standalone_message(
|
|
132
|
-
client: StandaloneA2AClientState,
|
|
133
|
-
message: str,
|
|
134
|
-
configuration: Optional[Dict[str, Any]] = None
|
|
126
|
+
client: StandaloneA2AClientState, message: str, configuration: Optional[Dict[str, Any]] = None
|
|
135
127
|
) -> str:
|
|
136
128
|
"""Pure function to send message"""
|
|
137
129
|
request = create_standalone_message_request(message, client.session_id, configuration)
|
|
@@ -145,9 +137,7 @@ async def send_standalone_message(
|
|
|
145
137
|
|
|
146
138
|
|
|
147
139
|
async def stream_standalone_message(
|
|
148
|
-
client: StandaloneA2AClientState,
|
|
149
|
-
message: str,
|
|
150
|
-
configuration: Optional[Dict[str, Any]] = None
|
|
140
|
+
client: StandaloneA2AClientState, message: str, configuration: Optional[Dict[str, Any]] = None
|
|
151
141
|
) -> AsyncGenerator[Dict[str, Any], None]:
|
|
152
142
|
"""Pure function to stream message"""
|
|
153
143
|
request = create_standalone_streaming_message_request(message, client.session_id, configuration)
|
|
@@ -161,12 +151,8 @@ async def stream_standalone_message(
|
|
|
161
151
|
"POST",
|
|
162
152
|
url,
|
|
163
153
|
json=request,
|
|
164
|
-
headers={
|
|
165
|
-
"Content-Type": "application/json",
|
|
166
|
-
"Accept": "text/event-stream"
|
|
167
|
-
}
|
|
154
|
+
headers={"Content-Type": "application/json", "Accept": "text/event-stream"},
|
|
168
155
|
) as response:
|
|
169
|
-
|
|
170
156
|
if not response.is_success:
|
|
171
157
|
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
|
172
158
|
|
|
@@ -197,10 +183,7 @@ async def get_standalone_agent_card(client: StandaloneA2AClientState) -> Dict[st
|
|
|
197
183
|
url = f"{client.config.base_url}/.well-known/agent-card"
|
|
198
184
|
|
|
199
185
|
async with httpx.AsyncClient(timeout=client.config.timeout / 1000.0) as http_client:
|
|
200
|
-
response = await http_client.get(
|
|
201
|
-
url,
|
|
202
|
-
headers={"Accept": "application/json"}
|
|
203
|
-
)
|
|
186
|
+
response = await http_client.get(url, headers={"Accept": "application/json"})
|
|
204
187
|
|
|
205
188
|
if not response.is_success:
|
|
206
189
|
raise Exception(f"Failed to get agent card: HTTP {response.status_code}")
|
|
@@ -212,7 +195,7 @@ async def send_standalone_message_to_agent(
|
|
|
212
195
|
client: StandaloneA2AClientState,
|
|
213
196
|
agent_name: str,
|
|
214
197
|
message: str,
|
|
215
|
-
configuration: Optional[Dict[str, Any]] = None
|
|
198
|
+
configuration: Optional[Dict[str, Any]] = None,
|
|
216
199
|
) -> str:
|
|
217
200
|
"""Pure function to send message to specific agent"""
|
|
218
201
|
request = create_standalone_message_request(message, client.session_id, configuration)
|
|
@@ -288,7 +271,9 @@ async def connect_to_standalone_a2a_agent(base_url: str) -> Dict[str, Any]:
|
|
|
288
271
|
async for event in stream_standalone_message(client, message, config):
|
|
289
272
|
yield event
|
|
290
273
|
|
|
291
|
-
async def ask_agent(
|
|
274
|
+
async def ask_agent(
|
|
275
|
+
agent_name: str, message: str, config: Optional[Dict[str, Any]] = None
|
|
276
|
+
) -> str:
|
|
292
277
|
return await send_standalone_message_to_agent(client, agent_name, message, config)
|
|
293
278
|
|
|
294
279
|
return {
|
|
@@ -296,7 +281,7 @@ async def connect_to_standalone_a2a_agent(base_url: str) -> Dict[str, Any]:
|
|
|
296
281
|
"agent_card": agent_card,
|
|
297
282
|
"ask": ask,
|
|
298
283
|
"stream": stream,
|
|
299
|
-
"ask_agent": ask_agent
|
|
284
|
+
"ask_agent": ask_agent,
|
|
300
285
|
}
|
|
301
286
|
|
|
302
287
|
|
|
@@ -308,7 +293,9 @@ async def main_example():
|
|
|
308
293
|
connection = await connect_to_standalone_a2a_agent("http://localhost:3000")
|
|
309
294
|
|
|
310
295
|
print("Connected to A2A server!")
|
|
311
|
-
print(
|
|
296
|
+
print(
|
|
297
|
+
f"Available agents: {[skill['name'] for skill in connection['agent_card']['skills']]}"
|
|
298
|
+
)
|
|
312
299
|
|
|
313
300
|
# Send a message
|
|
314
301
|
response = await connection["ask"]("Hello, how can you help me?")
|
jaf/a2a/tests/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ Comprehensive tests for the Agent-to-Agent Communication Protocol implementation
|
|
|
5
5
|
|
|
6
6
|
Test Structure:
|
|
7
7
|
- test_types.py: Core type validation and Pydantic model tests
|
|
8
|
-
- test_protocol.py: JSON-RPC protocol handler tests
|
|
8
|
+
- test_protocol.py: JSON-RPC protocol handler tests
|
|
9
9
|
- test_client.py: HTTP client functionality tests
|
|
10
10
|
- test_agent.py: Agent creation and execution tests
|
|
11
11
|
- test_integration.py: End-to-end integration tests
|
|
@@ -13,13 +13,13 @@ Test Structure:
|
|
|
13
13
|
Usage:
|
|
14
14
|
# Run all A2A tests
|
|
15
15
|
python -m pytest jaf/a2a/tests/
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
# Run specific test file
|
|
18
18
|
python -m pytest jaf/a2a/tests/test_types.py
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
# Run with verbose output
|
|
21
21
|
python -m pytest jaf/a2a/tests/ -v
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
# Run with coverage
|
|
24
24
|
python -m pytest jaf/a2a/tests/ --cov=jaf.a2a
|
|
25
25
|
|
|
@@ -55,20 +55,19 @@ def run_all_tests():
|
|
|
55
55
|
# Configure pytest for async tests
|
|
56
56
|
pytest_args = [
|
|
57
57
|
str(test_dir),
|
|
58
|
-
"-v",
|
|
59
|
-
"--tb=short",
|
|
58
|
+
"-v", # Verbose output
|
|
59
|
+
"--tb=short", # Short traceback format
|
|
60
60
|
"--asyncio-mode=auto", # Auto async mode
|
|
61
|
-
"-x",
|
|
61
|
+
"-x", # Stop on first failure
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
# Add coverage if available
|
|
65
65
|
try:
|
|
66
66
|
import pytest_cov
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"--cov-report=term-missing",
|
|
70
|
-
|
|
71
|
-
])
|
|
67
|
+
|
|
68
|
+
pytest_args.extend(
|
|
69
|
+
["--cov=jaf.a2a", "--cov-report=term-missing", "--cov-report=html:htmlcov"]
|
|
70
|
+
)
|
|
72
71
|
except ImportError:
|
|
73
72
|
print("pytest-cov not available, skipping coverage report")
|
|
74
73
|
|
|
@@ -93,12 +92,7 @@ def run_specific_test(test_name: str):
|
|
|
93
92
|
print(f"Test file not found: {test_path}")
|
|
94
93
|
return 1
|
|
95
94
|
|
|
96
|
-
pytest_args = [
|
|
97
|
-
str(test_path),
|
|
98
|
-
"-v",
|
|
99
|
-
"--tb=short",
|
|
100
|
-
"--asyncio-mode=auto"
|
|
101
|
-
]
|
|
95
|
+
pytest_args = [str(test_path), "-v", "--tb=short", "--asyncio-mode=auto"]
|
|
102
96
|
|
|
103
97
|
return pytest.main(pytest_args)
|
|
104
98
|
|
|
@@ -108,12 +102,7 @@ def run_integration_tests_only():
|
|
|
108
102
|
test_dir = Path(__file__).parent
|
|
109
103
|
integration_test = test_dir / "test_integration.py"
|
|
110
104
|
|
|
111
|
-
pytest_args = [
|
|
112
|
-
str(integration_test),
|
|
113
|
-
"-v",
|
|
114
|
-
"--tb=short",
|
|
115
|
-
"--asyncio-mode=auto"
|
|
116
|
-
]
|
|
105
|
+
pytest_args = [str(integration_test), "-v", "--tb=short", "--asyncio-mode=auto"]
|
|
117
106
|
|
|
118
107
|
return pytest.main(pytest_args)
|
|
119
108
|
|
|
@@ -127,7 +116,7 @@ def run_unit_tests_only():
|
|
|
127
116
|
"-v",
|
|
128
117
|
"--tb=short",
|
|
129
118
|
"--asyncio-mode=auto",
|
|
130
|
-
"--ignore=test_integration.py"
|
|
119
|
+
"--ignore=test_integration.py",
|
|
131
120
|
]
|
|
132
121
|
|
|
133
122
|
return pytest.main(pytest_args)
|
|
@@ -137,14 +126,8 @@ def run_unit_tests_only():
|
|
|
137
126
|
def pytest_configure(config):
|
|
138
127
|
"""Configure pytest for A2A tests"""
|
|
139
128
|
# Add custom markers
|
|
140
|
-
config.addinivalue_line(
|
|
141
|
-
|
|
142
|
-
"integration: mark test as integration test"
|
|
143
|
-
)
|
|
144
|
-
config.addinivalue_line(
|
|
145
|
-
"markers",
|
|
146
|
-
"slow: mark test as slow running"
|
|
147
|
-
)
|
|
129
|
+
config.addinivalue_line("markers", "integration: mark test as integration test")
|
|
130
|
+
config.addinivalue_line("markers", "slow: mark test as slow running")
|
|
148
131
|
|
|
149
132
|
|
|
150
133
|
# Test collection
|
jaf/a2a/tests/run_tests.py
CHANGED
|
@@ -93,10 +93,7 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
|
|
|
93
93
|
if mode == "all":
|
|
94
94
|
pytest_args.append(str(test_dir))
|
|
95
95
|
elif mode == "unit":
|
|
96
|
-
pytest_args.extend([
|
|
97
|
-
str(test_dir),
|
|
98
|
-
"--ignore=" + str(test_dir / "test_integration.py")
|
|
99
|
-
])
|
|
96
|
+
pytest_args.extend([str(test_dir), "--ignore=" + str(test_dir / "test_integration.py")])
|
|
100
97
|
elif mode == "integration":
|
|
101
98
|
pytest_args.append(str(test_dir / "test_integration.py"))
|
|
102
99
|
elif mode in ["types", "protocol", "client", "agent"]:
|
|
@@ -105,10 +102,7 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
|
|
|
105
102
|
pytest_args.append(str(test_dir))
|
|
106
103
|
|
|
107
104
|
# Add asyncio support
|
|
108
|
-
pytest_args.extend([
|
|
109
|
-
"--asyncio-mode=auto",
|
|
110
|
-
"--tb=short"
|
|
111
|
-
])
|
|
105
|
+
pytest_args.extend(["--asyncio-mode=auto", "--tb=short"])
|
|
112
106
|
|
|
113
107
|
# Verbosity
|
|
114
108
|
if args.verbose:
|
|
@@ -126,6 +120,7 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
|
|
|
126
120
|
if args.parallel:
|
|
127
121
|
try:
|
|
128
122
|
import pytest_xdist
|
|
123
|
+
|
|
129
124
|
pytest_args.extend(["-n", "auto"])
|
|
130
125
|
except ImportError:
|
|
131
126
|
print("⚠️ pytest-xdist not available, running sequentially")
|
|
@@ -134,10 +129,8 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
|
|
|
134
129
|
if args.coverage:
|
|
135
130
|
try:
|
|
136
131
|
import pytest_cov
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
"--cov-report=term-missing"
|
|
140
|
-
])
|
|
132
|
+
|
|
133
|
+
pytest_args.extend(["--cov=jaf.a2a", "--cov-report=term-missing"])
|
|
141
134
|
|
|
142
135
|
if args.html:
|
|
143
136
|
pytest_args.append("--cov-report=html:htmlcov")
|
|
@@ -151,10 +144,8 @@ def build_pytest_args(mode: str, args: argparse.Namespace) -> List[str]:
|
|
|
151
144
|
if args.html and not args.coverage:
|
|
152
145
|
try:
|
|
153
146
|
import pytest_html
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
"--self-contained-html"
|
|
157
|
-
])
|
|
147
|
+
|
|
148
|
+
pytest_args.extend(["--html=test_report.html", "--self-contained-html"])
|
|
158
149
|
except ImportError:
|
|
159
150
|
print("⚠️ pytest-html not available, skipping HTML report")
|
|
160
151
|
|
|
@@ -181,6 +172,7 @@ def run_tests(mode: str, args: argparse.Namespace) -> int:
|
|
|
181
172
|
# Import and run pytest
|
|
182
173
|
try:
|
|
183
174
|
import pytest
|
|
175
|
+
|
|
184
176
|
exit_code = pytest.main(pytest_args)
|
|
185
177
|
except ImportError:
|
|
186
178
|
print("❌ pytest not available")
|
|
@@ -210,7 +202,7 @@ def main():
|
|
|
210
202
|
parser = argparse.ArgumentParser(
|
|
211
203
|
description="JAF A2A Test Runner",
|
|
212
204
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
213
|
-
epilog=__doc__
|
|
205
|
+
epilog=__doc__,
|
|
214
206
|
)
|
|
215
207
|
|
|
216
208
|
parser.add_argument(
|
|
@@ -218,50 +210,22 @@ def main():
|
|
|
218
210
|
nargs="?",
|
|
219
211
|
default="all",
|
|
220
212
|
choices=["all", "unit", "integration", "types", "protocol", "client", "agent"],
|
|
221
|
-
help="Test mode to run (default: all)"
|
|
213
|
+
help="Test mode to run (default: all)",
|
|
222
214
|
)
|
|
223
215
|
|
|
224
|
-
parser.add_argument(
|
|
225
|
-
"-v", "--verbose",
|
|
226
|
-
action="store_true",
|
|
227
|
-
help="Verbose output"
|
|
228
|
-
)
|
|
216
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
|
|
229
217
|
|
|
230
|
-
parser.add_argument(
|
|
231
|
-
"-q", "--quiet",
|
|
232
|
-
action="store_true",
|
|
233
|
-
help="Minimal output"
|
|
234
|
-
)
|
|
218
|
+
parser.add_argument("-q", "--quiet", action="store_true", help="Minimal output")
|
|
235
219
|
|
|
236
|
-
parser.add_argument(
|
|
237
|
-
"--coverage",
|
|
238
|
-
action="store_true",
|
|
239
|
-
help="Generate coverage report"
|
|
240
|
-
)
|
|
220
|
+
parser.add_argument("--coverage", action="store_true", help="Generate coverage report")
|
|
241
221
|
|
|
242
|
-
parser.add_argument(
|
|
243
|
-
"--html",
|
|
244
|
-
action="store_true",
|
|
245
|
-
help="Generate HTML reports"
|
|
246
|
-
)
|
|
222
|
+
parser.add_argument("--html", action="store_true", help="Generate HTML reports")
|
|
247
223
|
|
|
248
|
-
parser.add_argument(
|
|
249
|
-
"--xml",
|
|
250
|
-
action="store_true",
|
|
251
|
-
help="Generate XML coverage report"
|
|
252
|
-
)
|
|
224
|
+
parser.add_argument("--xml", action="store_true", help="Generate XML coverage report")
|
|
253
225
|
|
|
254
|
-
parser.add_argument(
|
|
255
|
-
"--failfast",
|
|
256
|
-
action="store_true",
|
|
257
|
-
help="Stop on first failure"
|
|
258
|
-
)
|
|
226
|
+
parser.add_argument("--failfast", action="store_true", help="Stop on first failure")
|
|
259
227
|
|
|
260
|
-
parser.add_argument(
|
|
261
|
-
"--parallel",
|
|
262
|
-
action="store_true",
|
|
263
|
-
help="Run tests in parallel"
|
|
264
|
-
)
|
|
228
|
+
parser.add_argument("--parallel", action="store_true", help="Run tests in parallel")
|
|
265
229
|
|
|
266
230
|
args = parser.parse_args()
|
|
267
231
|
|