mockarty 0.1.0__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.
- mockarty/__init__.py +207 -0
- mockarty/_base_client.py +122 -0
- mockarty/api/__init__.py +73 -0
- mockarty/api/_base.py +75 -0
- mockarty/api/agent_tasks.py +105 -0
- mockarty/api/collections.py +86 -0
- mockarty/api/contracts.py +328 -0
- mockarty/api/environments.py +117 -0
- mockarty/api/folders.py +92 -0
- mockarty/api/fuzzing.py +436 -0
- mockarty/api/generator.py +101 -0
- mockarty/api/health.py +64 -0
- mockarty/api/imports.py +66 -0
- mockarty/api/mocks.py +396 -0
- mockarty/api/namespace_settings.py +207 -0
- mockarty/api/namespaces.py +80 -0
- mockarty/api/perf.py +296 -0
- mockarty/api/proxy.py +47 -0
- mockarty/api/recorder.py +333 -0
- mockarty/api/stats.py +57 -0
- mockarty/api/stores.py +125 -0
- mockarty/api/tags.py +48 -0
- mockarty/api/templates.py +76 -0
- mockarty/api/testruns.py +82 -0
- mockarty/api/undefined.py +85 -0
- mockarty/async_client.py +310 -0
- mockarty/builders/__init__.py +7 -0
- mockarty/builders/mock_builder.py +461 -0
- mockarty/client.py +312 -0
- mockarty/errors.py +56 -0
- mockarty/models/__init__.py +120 -0
- mockarty/models/admin.py +136 -0
- mockarty/models/common.py +131 -0
- mockarty/models/condition.py +50 -0
- mockarty/models/contexts.py +180 -0
- mockarty/models/contract.py +78 -0
- mockarty/models/folders.py +23 -0
- mockarty/models/fuzzing.py +45 -0
- mockarty/models/generator.py +39 -0
- mockarty/models/imports.py +20 -0
- mockarty/models/mock.py +223 -0
- mockarty/models/recorder.py +36 -0
- mockarty/models/store.py +28 -0
- mockarty/models/tags.py +21 -0
- mockarty/models/templates.py +19 -0
- mockarty/models/testrun.py +26 -0
- mockarty/models/undefined.py +26 -0
- mockarty/testing/__init__.py +7 -0
- mockarty/testing/fixtures.py +73 -0
- mockarty-0.1.0.dist-info/METADATA +232 -0
- mockarty-0.1.0.dist-info/RECORD +54 -0
- mockarty-0.1.0.dist-info/WHEEL +4 -0
- mockarty-0.1.0.dist-info/entry_points.txt +2 -0
- mockarty-0.1.0.dist-info/licenses/LICENSE +21 -0
mockarty/__init__.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Mockarty. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Mockarty Python SDK -- Official client for the Mockarty multi-protocol mock server.
|
|
4
|
+
|
|
5
|
+
Quick start::
|
|
6
|
+
|
|
7
|
+
from mockarty import MockartyClient, MockBuilder, AssertAction
|
|
8
|
+
|
|
9
|
+
with MockartyClient(base_url="http://localhost:5770") as client:
|
|
10
|
+
mock = (
|
|
11
|
+
MockBuilder.http("/api/users/:id", "GET")
|
|
12
|
+
.id("user-get")
|
|
13
|
+
.respond(200, body={"id": "$.pathParam.id", "name": "$.fake.FirstName"})
|
|
14
|
+
.build()
|
|
15
|
+
)
|
|
16
|
+
result = client.mocks.create(mock)
|
|
17
|
+
print(f"Created mock: {result.mock.id}")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from mockarty.api.agent_tasks import AgentTaskAPI, AsyncAgentTaskAPI
|
|
23
|
+
from mockarty.api.environments import AsyncEnvironmentAPI, EnvironmentAPI
|
|
24
|
+
from mockarty.api.folders import AsyncFolderAPI, FolderAPI
|
|
25
|
+
from mockarty.api.namespace_settings import (
|
|
26
|
+
AsyncNamespaceSettingsAPI,
|
|
27
|
+
NamespaceSettingsAPI,
|
|
28
|
+
)
|
|
29
|
+
from mockarty.api.proxy import AsyncProxyAPI, ProxyAPI
|
|
30
|
+
from mockarty.api.stats import AsyncStatsAPI, StatsAPI
|
|
31
|
+
from mockarty.api.tags import AsyncTagAPI, TagAPI
|
|
32
|
+
from mockarty.api.undefined import AsyncUndefinedAPI, UndefinedAPI
|
|
33
|
+
from mockarty.async_client import AsyncMockartyClient
|
|
34
|
+
from mockarty.builders.mock_builder import MockBuilder, OneOfBuilder
|
|
35
|
+
from mockarty.client import MockartyClient
|
|
36
|
+
from mockarty.errors import (
|
|
37
|
+
MockartyAPIError,
|
|
38
|
+
MockartyConflictError,
|
|
39
|
+
MockartyConnectionError,
|
|
40
|
+
MockartyError,
|
|
41
|
+
MockartyForbiddenError,
|
|
42
|
+
MockartyNotFoundError,
|
|
43
|
+
MockartyRateLimitError,
|
|
44
|
+
MockartyServerError,
|
|
45
|
+
MockartyTimeoutError,
|
|
46
|
+
MockartyUnauthorizedError,
|
|
47
|
+
)
|
|
48
|
+
from mockarty.models.common import (
|
|
49
|
+
Collection,
|
|
50
|
+
ErrorResponse,
|
|
51
|
+
HealthResponse,
|
|
52
|
+
MockLogs,
|
|
53
|
+
Page,
|
|
54
|
+
PerfComparison,
|
|
55
|
+
PerfConfig,
|
|
56
|
+
PerfResult,
|
|
57
|
+
PerfTask,
|
|
58
|
+
RequestLog,
|
|
59
|
+
TestRunResult,
|
|
60
|
+
)
|
|
61
|
+
from mockarty.models.condition import AssertAction, Condition
|
|
62
|
+
from mockarty.models.contexts import (
|
|
63
|
+
GraphQLRequestContext,
|
|
64
|
+
GrpcRequestContext,
|
|
65
|
+
HttpRequestContext,
|
|
66
|
+
KafkaRequestContext,
|
|
67
|
+
MCPRequestContext,
|
|
68
|
+
RabbitMQRequestContext,
|
|
69
|
+
SmtpRequestContext,
|
|
70
|
+
SoapRequestContext,
|
|
71
|
+
SocketRequestContext,
|
|
72
|
+
SSERequestContext,
|
|
73
|
+
)
|
|
74
|
+
from mockarty.models.contract import (
|
|
75
|
+
Contract,
|
|
76
|
+
ContractConfig,
|
|
77
|
+
ContractResult,
|
|
78
|
+
ContractValidationRequest,
|
|
79
|
+
ContractValidationResult,
|
|
80
|
+
ContractViolation,
|
|
81
|
+
)
|
|
82
|
+
from mockarty.models.folders import MockFolder
|
|
83
|
+
from mockarty.models.fuzzing import FuzzingConfig, FuzzingResult, FuzzingRun
|
|
84
|
+
from mockarty.models.generator import GeneratorPreview, GeneratorRequest, GeneratorResponse
|
|
85
|
+
from mockarty.models.imports import ImportResult
|
|
86
|
+
from mockarty.models.mock import (
|
|
87
|
+
Callback,
|
|
88
|
+
ContentResponse,
|
|
89
|
+
Extract,
|
|
90
|
+
Mock,
|
|
91
|
+
OneOf,
|
|
92
|
+
Proxy,
|
|
93
|
+
SaveMockResponse,
|
|
94
|
+
)
|
|
95
|
+
from mockarty.models.recorder import RecorderEntry, RecorderSession
|
|
96
|
+
from mockarty.models.store import DeleteFromStoreRequest, StoreData, StoreEntry
|
|
97
|
+
from mockarty.models.tags import Tag
|
|
98
|
+
from mockarty.models.templates import TemplateFile
|
|
99
|
+
from mockarty.models.testrun import TestRun
|
|
100
|
+
from mockarty.models.undefined import UndefinedRequest
|
|
101
|
+
|
|
102
|
+
__version__ = "0.1.0"
|
|
103
|
+
|
|
104
|
+
__all__ = [
|
|
105
|
+
# Clients
|
|
106
|
+
"MockartyClient",
|
|
107
|
+
"AsyncMockartyClient",
|
|
108
|
+
# Builders
|
|
109
|
+
"MockBuilder",
|
|
110
|
+
"OneOfBuilder",
|
|
111
|
+
# Core models
|
|
112
|
+
"Mock",
|
|
113
|
+
"ContentResponse",
|
|
114
|
+
"OneOf",
|
|
115
|
+
"Proxy",
|
|
116
|
+
"Callback",
|
|
117
|
+
"Extract",
|
|
118
|
+
"SaveMockResponse",
|
|
119
|
+
# Conditions
|
|
120
|
+
"Condition",
|
|
121
|
+
"AssertAction",
|
|
122
|
+
# Protocol contexts
|
|
123
|
+
"HttpRequestContext",
|
|
124
|
+
"GrpcRequestContext",
|
|
125
|
+
"MCPRequestContext",
|
|
126
|
+
"SocketRequestContext",
|
|
127
|
+
"SoapRequestContext",
|
|
128
|
+
"GraphQLRequestContext",
|
|
129
|
+
"SSERequestContext",
|
|
130
|
+
"KafkaRequestContext",
|
|
131
|
+
"RabbitMQRequestContext",
|
|
132
|
+
"SmtpRequestContext",
|
|
133
|
+
# Common models
|
|
134
|
+
"Page",
|
|
135
|
+
"HealthResponse",
|
|
136
|
+
"ErrorResponse",
|
|
137
|
+
"RequestLog",
|
|
138
|
+
"MockLogs",
|
|
139
|
+
"Collection",
|
|
140
|
+
"TestRunResult",
|
|
141
|
+
"PerfConfig",
|
|
142
|
+
"PerfTask",
|
|
143
|
+
"PerfResult",
|
|
144
|
+
"PerfComparison",
|
|
145
|
+
# Store models
|
|
146
|
+
"StoreEntry",
|
|
147
|
+
"StoreData",
|
|
148
|
+
"DeleteFromStoreRequest",
|
|
149
|
+
# Generator models
|
|
150
|
+
"GeneratorRequest",
|
|
151
|
+
"GeneratorPreview",
|
|
152
|
+
"GeneratorResponse",
|
|
153
|
+
# Fuzzing models
|
|
154
|
+
"FuzzingConfig",
|
|
155
|
+
"FuzzingRun",
|
|
156
|
+
"FuzzingResult",
|
|
157
|
+
# Contract models
|
|
158
|
+
"Contract",
|
|
159
|
+
"ContractConfig",
|
|
160
|
+
"ContractResult",
|
|
161
|
+
"ContractValidationRequest",
|
|
162
|
+
"ContractValidationResult",
|
|
163
|
+
"ContractViolation",
|
|
164
|
+
# Recorder models
|
|
165
|
+
"RecorderSession",
|
|
166
|
+
"RecorderEntry",
|
|
167
|
+
# Template models
|
|
168
|
+
"TemplateFile",
|
|
169
|
+
# Import models
|
|
170
|
+
"ImportResult",
|
|
171
|
+
# Test run models
|
|
172
|
+
"TestRun",
|
|
173
|
+
# Tag models
|
|
174
|
+
"Tag",
|
|
175
|
+
# Folder models
|
|
176
|
+
"MockFolder",
|
|
177
|
+
# Undefined request models
|
|
178
|
+
"UndefinedRequest",
|
|
179
|
+
# API resource classes
|
|
180
|
+
"TagAPI",
|
|
181
|
+
"AsyncTagAPI",
|
|
182
|
+
"FolderAPI",
|
|
183
|
+
"AsyncFolderAPI",
|
|
184
|
+
"UndefinedAPI",
|
|
185
|
+
"AsyncUndefinedAPI",
|
|
186
|
+
"StatsAPI",
|
|
187
|
+
"AsyncStatsAPI",
|
|
188
|
+
"AgentTaskAPI",
|
|
189
|
+
"AsyncAgentTaskAPI",
|
|
190
|
+
"NamespaceSettingsAPI",
|
|
191
|
+
"AsyncNamespaceSettingsAPI",
|
|
192
|
+
"ProxyAPI",
|
|
193
|
+
"AsyncProxyAPI",
|
|
194
|
+
"EnvironmentAPI",
|
|
195
|
+
"AsyncEnvironmentAPI",
|
|
196
|
+
# Errors
|
|
197
|
+
"MockartyError",
|
|
198
|
+
"MockartyAPIError",
|
|
199
|
+
"MockartyNotFoundError",
|
|
200
|
+
"MockartyUnauthorizedError",
|
|
201
|
+
"MockartyForbiddenError",
|
|
202
|
+
"MockartyConflictError",
|
|
203
|
+
"MockartyRateLimitError",
|
|
204
|
+
"MockartyServerError",
|
|
205
|
+
"MockartyConnectionError",
|
|
206
|
+
"MockartyTimeoutError",
|
|
207
|
+
]
|
mockarty/_base_client.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Mockarty. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Shared logic for sync and async Mockarty clients."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from mockarty.errors import (
|
|
13
|
+
MockartyAPIError,
|
|
14
|
+
MockartyConflictError,
|
|
15
|
+
MockartyConnectionError,
|
|
16
|
+
MockartyError,
|
|
17
|
+
MockartyForbiddenError,
|
|
18
|
+
MockartyNotFoundError,
|
|
19
|
+
MockartyRateLimitError,
|
|
20
|
+
MockartyServerError,
|
|
21
|
+
MockartyTimeoutError,
|
|
22
|
+
MockartyUnauthorizedError,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Maps HTTP status codes to specific exception classes.
|
|
26
|
+
_STATUS_EXCEPTION_MAP: dict[int, type[MockartyAPIError]] = {
|
|
27
|
+
401: MockartyUnauthorizedError,
|
|
28
|
+
403: MockartyForbiddenError,
|
|
29
|
+
404: MockartyNotFoundError,
|
|
30
|
+
409: MockartyConflictError,
|
|
31
|
+
429: MockartyRateLimitError,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
DEFAULT_BASE_URL = "http://localhost:5770"
|
|
35
|
+
DEFAULT_NAMESPACE = "sandbox"
|
|
36
|
+
DEFAULT_TIMEOUT = 30.0
|
|
37
|
+
DEFAULT_MAX_RETRIES = 3
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_base_url(base_url: str | None) -> str:
|
|
41
|
+
"""Resolve the base URL from the argument or the environment."""
|
|
42
|
+
if base_url is not None:
|
|
43
|
+
return base_url.rstrip("/")
|
|
44
|
+
return os.environ.get("MOCKARTY_BASE_URL", DEFAULT_BASE_URL).rstrip("/")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_api_key(api_key: str | None) -> str | None:
|
|
48
|
+
"""Resolve the API key from the argument or the environment."""
|
|
49
|
+
if api_key is not None:
|
|
50
|
+
return api_key
|
|
51
|
+
return os.environ.get("MOCKARTY_API_KEY")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def build_headers(api_key: str | None, namespace: str) -> dict[str, str]:
|
|
55
|
+
"""Build default headers for every request."""
|
|
56
|
+
headers: dict[str, str] = {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"Accept": "application/json",
|
|
59
|
+
"X-Namespace": namespace,
|
|
60
|
+
}
|
|
61
|
+
if api_key:
|
|
62
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
63
|
+
return headers
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def raise_for_status(response: httpx.Response) -> None:
|
|
67
|
+
"""Inspect an httpx response and raise the appropriate SDK exception."""
|
|
68
|
+
if response.is_success:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
status = response.status_code
|
|
72
|
+
request_id = response.headers.get("X-Request-Id")
|
|
73
|
+
|
|
74
|
+
# Try to extract error message from JSON body
|
|
75
|
+
try:
|
|
76
|
+
body = response.json()
|
|
77
|
+
message = body.get("error", response.text)
|
|
78
|
+
except Exception:
|
|
79
|
+
message = response.text or f"HTTP {status}"
|
|
80
|
+
|
|
81
|
+
exc_cls = _STATUS_EXCEPTION_MAP.get(status)
|
|
82
|
+
if exc_cls is not None:
|
|
83
|
+
raise exc_cls(status_code=status, message=message, request_id=request_id)
|
|
84
|
+
|
|
85
|
+
if status >= 500:
|
|
86
|
+
raise MockartyServerError(
|
|
87
|
+
status_code=status, message=message, request_id=request_id
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
raise MockartyAPIError(
|
|
91
|
+
status_code=status, message=message, request_id=request_id
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def wrap_transport_error(exc: Exception) -> MockartyError: # type: ignore[return]
|
|
96
|
+
"""Convert httpx transport errors into SDK exceptions."""
|
|
97
|
+
if isinstance(exc, httpx.TimeoutException):
|
|
98
|
+
raise MockartyTimeoutError(str(exc)) from exc
|
|
99
|
+
if isinstance(exc, httpx.ConnectError):
|
|
100
|
+
raise MockartyConnectionError(str(exc)) from exc
|
|
101
|
+
if isinstance(exc, (httpx.HTTPStatusError, httpx.HTTPError)):
|
|
102
|
+
raise MockartyConnectionError(str(exc)) from exc
|
|
103
|
+
raise MockartyConnectionError(str(exc)) from exc
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def build_transport(max_retries: int) -> httpx.HTTPTransport:
|
|
107
|
+
"""Build an httpx transport with retry configuration."""
|
|
108
|
+
return httpx.HTTPTransport(retries=max_retries)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def build_async_transport(max_retries: int) -> httpx.AsyncHTTPTransport:
|
|
112
|
+
"""Build an async httpx transport with retry configuration."""
|
|
113
|
+
return httpx.AsyncHTTPTransport(retries=max_retries)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def serialize_body(obj: Any) -> Any:
|
|
117
|
+
"""Serialize a Pydantic model or dict to a JSON-compatible dict."""
|
|
118
|
+
if obj is None:
|
|
119
|
+
return None
|
|
120
|
+
if hasattr(obj, "model_dump"):
|
|
121
|
+
return obj.model_dump(by_alias=True, exclude_none=True)
|
|
122
|
+
return obj
|
mockarty/api/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Mockarty. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""API resource classes for Mockarty client."""
|
|
4
|
+
|
|
5
|
+
from mockarty.api.agent_tasks import AgentTaskAPI, AsyncAgentTaskAPI
|
|
6
|
+
from mockarty.api.collections import AsyncCollectionAPI, CollectionAPI
|
|
7
|
+
from mockarty.api.contracts import AsyncContractAPI, ContractAPI
|
|
8
|
+
from mockarty.api.environments import AsyncEnvironmentAPI, EnvironmentAPI
|
|
9
|
+
from mockarty.api.folders import AsyncFolderAPI, FolderAPI
|
|
10
|
+
from mockarty.api.fuzzing import AsyncFuzzingAPI, FuzzingAPI
|
|
11
|
+
from mockarty.api.generator import AsyncGeneratorAPI, GeneratorAPI
|
|
12
|
+
from mockarty.api.health import AsyncHealthAPI, HealthAPI
|
|
13
|
+
from mockarty.api.imports import AsyncImportAPI, ImportAPI
|
|
14
|
+
from mockarty.api.mocks import AsyncMockAPI, MockAPI
|
|
15
|
+
from mockarty.api.namespace_settings import (
|
|
16
|
+
AsyncNamespaceSettingsAPI,
|
|
17
|
+
NamespaceSettingsAPI,
|
|
18
|
+
)
|
|
19
|
+
from mockarty.api.namespaces import AsyncNamespaceAPI, NamespaceAPI
|
|
20
|
+
from mockarty.api.perf import AsyncPerfAPI, PerfAPI
|
|
21
|
+
from mockarty.api.proxy import AsyncProxyAPI, ProxyAPI
|
|
22
|
+
from mockarty.api.recorder import AsyncRecorderAPI, RecorderAPI
|
|
23
|
+
from mockarty.api.stats import AsyncStatsAPI, StatsAPI
|
|
24
|
+
from mockarty.api.stores import AsyncStoreAPI, StoreAPI
|
|
25
|
+
from mockarty.api.tags import AsyncTagAPI, TagAPI
|
|
26
|
+
from mockarty.api.templates import AsyncTemplateAPI, TemplateAPI
|
|
27
|
+
from mockarty.api.testruns import AsyncTestRunAPI, TestRunAPI
|
|
28
|
+
from mockarty.api.undefined import AsyncUndefinedAPI, UndefinedAPI
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"AgentTaskAPI",
|
|
32
|
+
"AsyncAgentTaskAPI",
|
|
33
|
+
"AsyncCollectionAPI",
|
|
34
|
+
"AsyncContractAPI",
|
|
35
|
+
"AsyncEnvironmentAPI",
|
|
36
|
+
"AsyncFolderAPI",
|
|
37
|
+
"AsyncFuzzingAPI",
|
|
38
|
+
"AsyncGeneratorAPI",
|
|
39
|
+
"AsyncHealthAPI",
|
|
40
|
+
"AsyncImportAPI",
|
|
41
|
+
"AsyncMockAPI",
|
|
42
|
+
"AsyncNamespaceAPI",
|
|
43
|
+
"AsyncNamespaceSettingsAPI",
|
|
44
|
+
"AsyncPerfAPI",
|
|
45
|
+
"AsyncProxyAPI",
|
|
46
|
+
"AsyncRecorderAPI",
|
|
47
|
+
"AsyncStatsAPI",
|
|
48
|
+
"AsyncStoreAPI",
|
|
49
|
+
"AsyncTagAPI",
|
|
50
|
+
"AsyncTemplateAPI",
|
|
51
|
+
"AsyncTestRunAPI",
|
|
52
|
+
"AsyncUndefinedAPI",
|
|
53
|
+
"CollectionAPI",
|
|
54
|
+
"ContractAPI",
|
|
55
|
+
"EnvironmentAPI",
|
|
56
|
+
"FolderAPI",
|
|
57
|
+
"FuzzingAPI",
|
|
58
|
+
"GeneratorAPI",
|
|
59
|
+
"HealthAPI",
|
|
60
|
+
"ImportAPI",
|
|
61
|
+
"MockAPI",
|
|
62
|
+
"NamespaceAPI",
|
|
63
|
+
"NamespaceSettingsAPI",
|
|
64
|
+
"PerfAPI",
|
|
65
|
+
"ProxyAPI",
|
|
66
|
+
"RecorderAPI",
|
|
67
|
+
"StatsAPI",
|
|
68
|
+
"StoreAPI",
|
|
69
|
+
"TagAPI",
|
|
70
|
+
"TemplateAPI",
|
|
71
|
+
"TestRunAPI",
|
|
72
|
+
"UndefinedAPI",
|
|
73
|
+
]
|
mockarty/api/_base.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Mockarty. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Base classes for sync and async API resources."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
from mockarty._base_client import raise_for_status, serialize_body, wrap_transport_error
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SyncAPIBase:
|
|
15
|
+
"""Base class for synchronous API resource groups."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, client: httpx.Client, namespace: str) -> None:
|
|
18
|
+
self._client = client
|
|
19
|
+
self._namespace = namespace
|
|
20
|
+
|
|
21
|
+
def _request(
|
|
22
|
+
self,
|
|
23
|
+
method: str,
|
|
24
|
+
path: str,
|
|
25
|
+
*,
|
|
26
|
+
json: Any = None,
|
|
27
|
+
params: dict[str, Any] | None = None,
|
|
28
|
+
headers: dict[str, str] | None = None,
|
|
29
|
+
) -> httpx.Response:
|
|
30
|
+
"""Execute an HTTP request and raise on errors."""
|
|
31
|
+
kwargs: dict[str, Any] = {"params": params}
|
|
32
|
+
if json is not None:
|
|
33
|
+
kwargs["json"] = serialize_body(json)
|
|
34
|
+
if headers:
|
|
35
|
+
kwargs["headers"] = headers
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
response = self._client.request(method, path, **kwargs)
|
|
39
|
+
except Exception as exc:
|
|
40
|
+
wrap_transport_error(exc)
|
|
41
|
+
|
|
42
|
+
raise_for_status(response)
|
|
43
|
+
return response
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AsyncAPIBase:
|
|
47
|
+
"""Base class for asynchronous API resource groups."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, client: httpx.AsyncClient, namespace: str) -> None:
|
|
50
|
+
self._client = client
|
|
51
|
+
self._namespace = namespace
|
|
52
|
+
|
|
53
|
+
async def _request(
|
|
54
|
+
self,
|
|
55
|
+
method: str,
|
|
56
|
+
path: str,
|
|
57
|
+
*,
|
|
58
|
+
json: Any = None,
|
|
59
|
+
params: dict[str, Any] | None = None,
|
|
60
|
+
headers: dict[str, str] | None = None,
|
|
61
|
+
) -> httpx.Response:
|
|
62
|
+
"""Execute an async HTTP request and raise on errors."""
|
|
63
|
+
kwargs: dict[str, Any] = {"params": params}
|
|
64
|
+
if json is not None:
|
|
65
|
+
kwargs["json"] = serialize_body(json)
|
|
66
|
+
if headers:
|
|
67
|
+
kwargs["headers"] = headers
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
response = await self._client.request(method, path, **kwargs)
|
|
71
|
+
except Exception as exc:
|
|
72
|
+
wrap_transport_error(exc)
|
|
73
|
+
|
|
74
|
+
raise_for_status(response)
|
|
75
|
+
return response
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Mockarty. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Agent tasks API resource for AI-assisted mock generation."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from mockarty.api._base import AsyncAPIBase, SyncAPIBase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AgentTaskAPI(SyncAPIBase):
|
|
13
|
+
"""Synchronous Agent Task API resource."""
|
|
14
|
+
|
|
15
|
+
def list(self) -> list[dict[str, Any]]:
|
|
16
|
+
"""List all agent tasks."""
|
|
17
|
+
resp = self._request("GET", "/api/v1/agent-tasks")
|
|
18
|
+
data = resp.json()
|
|
19
|
+
if isinstance(data, list):
|
|
20
|
+
return data
|
|
21
|
+
if isinstance(data, dict):
|
|
22
|
+
return data.get("items") or data.get("tasks") or []
|
|
23
|
+
return []
|
|
24
|
+
|
|
25
|
+
def get(self, task_id: str) -> dict[str, Any]:
|
|
26
|
+
"""Get an agent task by ID."""
|
|
27
|
+
resp = self._request("GET", f"/api/v1/agent-tasks/{task_id}")
|
|
28
|
+
return resp.json()
|
|
29
|
+
|
|
30
|
+
def submit(self, task: dict[str, Any]) -> dict[str, Any]:
|
|
31
|
+
"""Submit a new agent task."""
|
|
32
|
+
resp = self._request("POST", "/api/v1/agent-tasks", json=task)
|
|
33
|
+
return resp.json()
|
|
34
|
+
|
|
35
|
+
def cancel(self, task_id: str) -> None:
|
|
36
|
+
"""Cancel a running agent task."""
|
|
37
|
+
self._request("POST", f"/api/v1/agent-tasks/{task_id}/cancel")
|
|
38
|
+
|
|
39
|
+
def delete(self, task_id: str) -> None:
|
|
40
|
+
"""Delete an agent task."""
|
|
41
|
+
self._request("DELETE", f"/api/v1/agent-tasks/{task_id}")
|
|
42
|
+
|
|
43
|
+
def clear_all(self) -> None:
|
|
44
|
+
"""Delete all agent tasks."""
|
|
45
|
+
self._request("DELETE", "/api/v1/agent-tasks")
|
|
46
|
+
|
|
47
|
+
def rerun(self, task_id: str) -> dict[str, Any]:
|
|
48
|
+
"""Re-run a completed agent task."""
|
|
49
|
+
resp = self._request("POST", f"/api/v1/agent-tasks/{task_id}/rerun")
|
|
50
|
+
return resp.json()
|
|
51
|
+
|
|
52
|
+
def export(self, task_id: str) -> bytes:
|
|
53
|
+
"""Export an agent task result as raw bytes."""
|
|
54
|
+
resp = self._request("GET", f"/api/v1/agent-tasks/{task_id}/export")
|
|
55
|
+
return resp.content
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AsyncAgentTaskAPI(AsyncAPIBase):
|
|
59
|
+
"""Asynchronous Agent Task API resource."""
|
|
60
|
+
|
|
61
|
+
async def list(self) -> list[dict[str, Any]]:
|
|
62
|
+
"""List all agent tasks."""
|
|
63
|
+
resp = await self._request("GET", "/api/v1/agent-tasks")
|
|
64
|
+
data = resp.json()
|
|
65
|
+
if isinstance(data, list):
|
|
66
|
+
return data
|
|
67
|
+
if isinstance(data, dict):
|
|
68
|
+
return data.get("items") or data.get("tasks") or []
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
async def get(self, task_id: str) -> dict[str, Any]:
|
|
72
|
+
"""Get an agent task by ID."""
|
|
73
|
+
resp = await self._request("GET", f"/api/v1/agent-tasks/{task_id}")
|
|
74
|
+
return resp.json()
|
|
75
|
+
|
|
76
|
+
async def submit(self, task: dict[str, Any]) -> dict[str, Any]:
|
|
77
|
+
"""Submit a new agent task."""
|
|
78
|
+
resp = await self._request("POST", "/api/v1/agent-tasks", json=task)
|
|
79
|
+
return resp.json()
|
|
80
|
+
|
|
81
|
+
async def cancel(self, task_id: str) -> None:
|
|
82
|
+
"""Cancel a running agent task."""
|
|
83
|
+
await self._request("POST", f"/api/v1/agent-tasks/{task_id}/cancel")
|
|
84
|
+
|
|
85
|
+
async def delete(self, task_id: str) -> None:
|
|
86
|
+
"""Delete an agent task."""
|
|
87
|
+
await self._request("DELETE", f"/api/v1/agent-tasks/{task_id}")
|
|
88
|
+
|
|
89
|
+
async def clear_all(self) -> None:
|
|
90
|
+
"""Delete all agent tasks."""
|
|
91
|
+
await self._request("DELETE", "/api/v1/agent-tasks")
|
|
92
|
+
|
|
93
|
+
async def rerun(self, task_id: str) -> dict[str, Any]:
|
|
94
|
+
"""Re-run a completed agent task."""
|
|
95
|
+
resp = await self._request(
|
|
96
|
+
"POST", f"/api/v1/agent-tasks/{task_id}/rerun"
|
|
97
|
+
)
|
|
98
|
+
return resp.json()
|
|
99
|
+
|
|
100
|
+
async def export(self, task_id: str) -> bytes:
|
|
101
|
+
"""Export an agent task result as raw bytes."""
|
|
102
|
+
resp = await self._request(
|
|
103
|
+
"GET", f"/api/v1/agent-tasks/{task_id}/export"
|
|
104
|
+
)
|
|
105
|
+
return resp.content
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Mockarty. All rights reserved.
|
|
2
|
+
|
|
3
|
+
"""Collection (API Tester) API resource."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from mockarty.api._base import AsyncAPIBase, SyncAPIBase
|
|
8
|
+
from mockarty.models.common import Collection, TestRunResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CollectionAPI(SyncAPIBase):
|
|
12
|
+
"""Synchronous Collection API resource."""
|
|
13
|
+
|
|
14
|
+
def list(self) -> list[Collection]:
|
|
15
|
+
"""List all test collections."""
|
|
16
|
+
resp = self._request("GET", "/api/v1/api-tester/collections")
|
|
17
|
+
data = resp.json()
|
|
18
|
+
if isinstance(data, list):
|
|
19
|
+
return [Collection.model_validate(c) for c in data]
|
|
20
|
+
if isinstance(data, dict):
|
|
21
|
+
items = data.get("items") or data.get("collections") or []
|
|
22
|
+
return [Collection.model_validate(c) for c in items]
|
|
23
|
+
return []
|
|
24
|
+
|
|
25
|
+
def get(self, collection_id: str) -> Collection:
|
|
26
|
+
"""Get a single collection by ID."""
|
|
27
|
+
resp = self._request("GET", f"/api/v1/api-tester/collections/{collection_id}")
|
|
28
|
+
return Collection.model_validate(resp.json())
|
|
29
|
+
|
|
30
|
+
def execute(self, collection_id: str) -> TestRunResult:
|
|
31
|
+
"""Execute all tests in a collection."""
|
|
32
|
+
resp = self._request("POST", f"/api/v1/api-tester/collections/{collection_id}/run")
|
|
33
|
+
return TestRunResult.model_validate(resp.json())
|
|
34
|
+
|
|
35
|
+
def execute_multiple(self, ids: list[str]) -> TestRunResult:
|
|
36
|
+
"""Execute tests from multiple collections."""
|
|
37
|
+
resp = self._request(
|
|
38
|
+
"POST", "/api/v1/api-tester/collections/run-multiple", json={"collectionIds": ids}
|
|
39
|
+
)
|
|
40
|
+
return TestRunResult.model_validate(resp.json())
|
|
41
|
+
|
|
42
|
+
def export(self, collection_id: str) -> bytes:
|
|
43
|
+
"""Export a collection as a downloadable archive."""
|
|
44
|
+
resp = self._request("GET", f"/api/v1/api-tester/collections/{collection_id}/export")
|
|
45
|
+
return resp.content
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class AsyncCollectionAPI(AsyncAPIBase):
|
|
49
|
+
"""Asynchronous Collection API resource."""
|
|
50
|
+
|
|
51
|
+
async def list(self) -> list[Collection]:
|
|
52
|
+
"""List all test collections."""
|
|
53
|
+
resp = await self._request("GET", "/api/v1/api-tester/collections")
|
|
54
|
+
data = resp.json()
|
|
55
|
+
if isinstance(data, list):
|
|
56
|
+
return [Collection.model_validate(c) for c in data]
|
|
57
|
+
if isinstance(data, dict):
|
|
58
|
+
items = data.get("items") or data.get("collections") or []
|
|
59
|
+
return [Collection.model_validate(c) for c in items]
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
async def get(self, collection_id: str) -> Collection:
|
|
63
|
+
"""Get a single collection by ID."""
|
|
64
|
+
resp = await self._request("GET", f"/api/v1/api-tester/collections/{collection_id}")
|
|
65
|
+
return Collection.model_validate(resp.json())
|
|
66
|
+
|
|
67
|
+
async def execute(self, collection_id: str) -> TestRunResult:
|
|
68
|
+
"""Execute all tests in a collection."""
|
|
69
|
+
resp = await self._request(
|
|
70
|
+
"POST", f"/api/v1/api-tester/collections/{collection_id}/run"
|
|
71
|
+
)
|
|
72
|
+
return TestRunResult.model_validate(resp.json())
|
|
73
|
+
|
|
74
|
+
async def execute_multiple(self, ids: list[str]) -> TestRunResult:
|
|
75
|
+
"""Execute tests from multiple collections."""
|
|
76
|
+
resp = await self._request(
|
|
77
|
+
"POST", "/api/v1/api-tester/collections/run-multiple", json={"collectionIds": ids}
|
|
78
|
+
)
|
|
79
|
+
return TestRunResult.model_validate(resp.json())
|
|
80
|
+
|
|
81
|
+
async def export(self, collection_id: str) -> bytes:
|
|
82
|
+
"""Export a collection as a downloadable archive."""
|
|
83
|
+
resp = await self._request(
|
|
84
|
+
"GET", f"/api/v1/api-tester/collections/{collection_id}/export"
|
|
85
|
+
)
|
|
86
|
+
return resp.content
|