mockserver-client 5.16.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.
- mockserver/__init__.py +85 -0
- mockserver/async_client.py +425 -0
- mockserver/client.py +207 -0
- mockserver/exceptions.py +21 -0
- mockserver/fluent.py +116 -0
- mockserver/models.py +1358 -0
- mockserver/websocket_client.py +314 -0
- mockserver_client-5.16.0.dist-info/METADATA +291 -0
- mockserver_client-5.16.0.dist-info/RECORD +11 -0
- mockserver_client-5.16.0.dist-info/WHEEL +5 -0
- mockserver_client-5.16.0.dist-info/top_level.txt +1 -0
mockserver/models.py
ADDED
|
@@ -0,0 +1,1358 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_FIELD_MAP = {
|
|
8
|
+
"status_code": "statusCode",
|
|
9
|
+
"reason_phrase": "reasonPhrase",
|
|
10
|
+
"keep_alive": "keepAlive",
|
|
11
|
+
"respond_before_body": "respondBeforeBody",
|
|
12
|
+
"query_string_parameters": "queryStringParameters",
|
|
13
|
+
"path_parameters": "pathParameters",
|
|
14
|
+
"socket_address": "socketAddress",
|
|
15
|
+
"time_unit": "timeUnit",
|
|
16
|
+
"time_to_live": "timeToLive",
|
|
17
|
+
"remaining_times": "remainingTimes",
|
|
18
|
+
"close_socket": "closeSocket",
|
|
19
|
+
"close_socket_delay": "closeSocketDelay",
|
|
20
|
+
"suppress_content_length_header": "suppressContentLengthHeader",
|
|
21
|
+
"content_length_header_override": "contentLengthHeaderOverride",
|
|
22
|
+
"suppress_connection_header": "suppressConnectionHeader",
|
|
23
|
+
"keep_alive_override": "keepAliveOverride",
|
|
24
|
+
"connection_options": "connectionOptions",
|
|
25
|
+
"callback_class": "callbackClass",
|
|
26
|
+
"client_id": "clientId",
|
|
27
|
+
"response_callback": "responseCallback",
|
|
28
|
+
"drop_connection": "dropConnection",
|
|
29
|
+
"response_bytes": "responseBytes",
|
|
30
|
+
"http_request": "httpRequest",
|
|
31
|
+
"http_response": "httpResponse",
|
|
32
|
+
"http_response_template": "httpResponseTemplate",
|
|
33
|
+
"http_response_class_callback": "httpResponseClassCallback",
|
|
34
|
+
"http_response_object_callback": "httpResponseObjectCallback",
|
|
35
|
+
"http_forward": "httpForward",
|
|
36
|
+
"http_forward_template": "httpForwardTemplate",
|
|
37
|
+
"http_forward_class_callback": "httpForwardClassCallback",
|
|
38
|
+
"http_forward_object_callback": "httpForwardObjectCallback",
|
|
39
|
+
"http_override_forwarded_request": "httpOverrideForwardedRequest",
|
|
40
|
+
"http_error": "httpError",
|
|
41
|
+
"template_type": "templateType",
|
|
42
|
+
"base64_bytes": "base64Bytes",
|
|
43
|
+
"not_body": "not",
|
|
44
|
+
"content_type": "contentType",
|
|
45
|
+
"at_least": "atLeast",
|
|
46
|
+
"at_most": "atMost",
|
|
47
|
+
"expectation_id": "expectationId",
|
|
48
|
+
"expectation_ids": "expectationIds",
|
|
49
|
+
"http_requests": "httpRequests",
|
|
50
|
+
"spec_url_or_payload": "specUrlOrPayload",
|
|
51
|
+
"operations_and_responses": "operationsAndResponses",
|
|
52
|
+
"operation_id": "operationId",
|
|
53
|
+
"request_modifier": "requestModifier",
|
|
54
|
+
"response_modifier": "responseModifier",
|
|
55
|
+
"maximum_number_of_request_to_return_in_verification_failure": "maximumNumberOfRequestToReturnInVerificationFailure",
|
|
56
|
+
"http_sse_response": "httpSseResponse",
|
|
57
|
+
"http_websocket_response": "httpWebSocketResponse",
|
|
58
|
+
"after_actions": "afterActions",
|
|
59
|
+
"base_path": "basePath",
|
|
60
|
+
"id_field": "idField",
|
|
61
|
+
"id_strategy": "idStrategy",
|
|
62
|
+
"initial_data": "initialData",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _to_camel(snake_str: str) -> str:
|
|
67
|
+
if snake_str in _FIELD_MAP:
|
|
68
|
+
return _FIELD_MAP[snake_str]
|
|
69
|
+
parts = snake_str.split("_")
|
|
70
|
+
return parts[0] + "".join(p.capitalize() for p in parts[1:])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _strip_none(d: dict) -> dict:
|
|
74
|
+
return {k: v for k, v in d.items() if v is not None}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _serialize_value(value: Any) -> Any:
|
|
78
|
+
if hasattr(value, "to_dict"):
|
|
79
|
+
return value.to_dict()
|
|
80
|
+
if isinstance(value, list):
|
|
81
|
+
return [_serialize_value(item) for item in value]
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
_REVERSE_FIELD_MAP = {v: k for k, v in _FIELD_MAP.items()}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _from_camel(camel_str: str) -> str:
|
|
89
|
+
if camel_str in _REVERSE_FIELD_MAP:
|
|
90
|
+
return _REVERSE_FIELD_MAP[camel_str]
|
|
91
|
+
result = []
|
|
92
|
+
for ch in camel_str:
|
|
93
|
+
if ch.isupper():
|
|
94
|
+
result.append("_")
|
|
95
|
+
result.append(ch.lower())
|
|
96
|
+
else:
|
|
97
|
+
result.append(ch)
|
|
98
|
+
return "".join(result)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _serialize_body(body: Body | str | dict | None) -> Any:
|
|
102
|
+
if body is None:
|
|
103
|
+
return None
|
|
104
|
+
if isinstance(body, str):
|
|
105
|
+
return body
|
|
106
|
+
if isinstance(body, dict):
|
|
107
|
+
return body
|
|
108
|
+
if hasattr(body, "to_dict"):
|
|
109
|
+
return body.to_dict()
|
|
110
|
+
return body
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
_BODY_TYPES = {"STRING", "JSON", "REGEX", "XML", "BINARY", "JSON_SCHEMA", "JSON_PATH", "XPATH", "XML_SCHEMA", "JSON_RPC", "GRAPHQL"}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _deserialize_body(data: Any) -> Body | JsonRpcBody | str | dict | None:
|
|
117
|
+
if data is None:
|
|
118
|
+
return None
|
|
119
|
+
if isinstance(data, str):
|
|
120
|
+
return data
|
|
121
|
+
if isinstance(data, dict):
|
|
122
|
+
if data.get("type") == "JSON_RPC":
|
|
123
|
+
return JsonRpcBody(
|
|
124
|
+
method=data.get("method", ""),
|
|
125
|
+
params_schema=data.get("paramsSchema"),
|
|
126
|
+
not_body=data.get("not", False),
|
|
127
|
+
optional=data.get("optional", False),
|
|
128
|
+
)
|
|
129
|
+
if data.get("type") == "GRAPHQL":
|
|
130
|
+
return GraphQLBody(
|
|
131
|
+
query=data.get("query", ""),
|
|
132
|
+
operation_name=data.get("operationName"),
|
|
133
|
+
variables_schema=data.get("variablesSchema"),
|
|
134
|
+
not_body=data.get("not", False),
|
|
135
|
+
optional=data.get("optional", False),
|
|
136
|
+
)
|
|
137
|
+
if data.get("type") in _BODY_TYPES:
|
|
138
|
+
return Body.from_dict(data)
|
|
139
|
+
return data
|
|
140
|
+
return data
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _serialize_key_multi_values(items: list[KeyToMultiValue] | None) -> list[dict] | None:
|
|
144
|
+
if items is None:
|
|
145
|
+
return None
|
|
146
|
+
return [item.to_dict() for item in items]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _deserialize_key_multi_values(data: list | dict | None) -> list[KeyToMultiValue] | None:
|
|
150
|
+
if data is None:
|
|
151
|
+
return None
|
|
152
|
+
if isinstance(data, dict):
|
|
153
|
+
return [
|
|
154
|
+
KeyToMultiValue(name=k, values=v if isinstance(v, list) else [v])
|
|
155
|
+
for k, v in data.items()
|
|
156
|
+
]
|
|
157
|
+
result = []
|
|
158
|
+
for item in data:
|
|
159
|
+
if isinstance(item, dict):
|
|
160
|
+
result.append(KeyToMultiValue.from_dict(item))
|
|
161
|
+
elif isinstance(item, str):
|
|
162
|
+
result.append(KeyToMultiValue(name=item, values=[]))
|
|
163
|
+
else:
|
|
164
|
+
result.append(KeyToMultiValue.from_dict(item))
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@dataclass
|
|
169
|
+
class DelayDistribution:
|
|
170
|
+
type: str | None = None
|
|
171
|
+
min: int | None = None
|
|
172
|
+
max: int | None = None
|
|
173
|
+
median: int | None = None
|
|
174
|
+
p99: int | None = None
|
|
175
|
+
mean: int | None = None
|
|
176
|
+
std_dev: int | None = None
|
|
177
|
+
|
|
178
|
+
def to_dict(self) -> dict:
|
|
179
|
+
return _strip_none({
|
|
180
|
+
"type": self.type,
|
|
181
|
+
"min": self.min,
|
|
182
|
+
"max": self.max,
|
|
183
|
+
"median": self.median,
|
|
184
|
+
"p99": self.p99,
|
|
185
|
+
"mean": self.mean,
|
|
186
|
+
"stdDev": self.std_dev,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def from_dict(cls, data: dict) -> DelayDistribution:
|
|
191
|
+
if data is None:
|
|
192
|
+
return None
|
|
193
|
+
return cls(
|
|
194
|
+
type=data.get("type"),
|
|
195
|
+
min=data.get("min"),
|
|
196
|
+
max=data.get("max"),
|
|
197
|
+
median=data.get("median"),
|
|
198
|
+
p99=data.get("p99"),
|
|
199
|
+
mean=data.get("mean"),
|
|
200
|
+
std_dev=data.get("stdDev"),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class Delay:
|
|
206
|
+
time_unit: str = "MILLISECONDS"
|
|
207
|
+
value: int = 0
|
|
208
|
+
distribution: DelayDistribution | None = None
|
|
209
|
+
|
|
210
|
+
def to_dict(self) -> dict:
|
|
211
|
+
return _strip_none({
|
|
212
|
+
"timeUnit": self.time_unit,
|
|
213
|
+
"value": self.value,
|
|
214
|
+
"distribution": self.distribution.to_dict() if self.distribution else None,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
@classmethod
|
|
218
|
+
def from_dict(cls, data: dict) -> Delay:
|
|
219
|
+
if data is None:
|
|
220
|
+
return None
|
|
221
|
+
dist_data = data.get("distribution")
|
|
222
|
+
return cls(
|
|
223
|
+
time_unit=data.get("timeUnit", "MILLISECONDS"),
|
|
224
|
+
value=data.get("value", 0),
|
|
225
|
+
distribution=DelayDistribution.from_dict(dist_data) if dist_data else None,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass
|
|
230
|
+
class Times:
|
|
231
|
+
remaining_times: int | None = None
|
|
232
|
+
unlimited: bool | None = None
|
|
233
|
+
|
|
234
|
+
def to_dict(self) -> dict:
|
|
235
|
+
return _strip_none({
|
|
236
|
+
"remainingTimes": self.remaining_times,
|
|
237
|
+
"unlimited": self.unlimited,
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def from_dict(cls, data: dict) -> Times:
|
|
242
|
+
if data is None:
|
|
243
|
+
return None
|
|
244
|
+
return cls(
|
|
245
|
+
remaining_times=data.get("remainingTimes"),
|
|
246
|
+
unlimited=data.get("unlimited"),
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _times_unlimited() -> Times:
|
|
251
|
+
return Times(unlimited=True)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _times_exactly(count: int) -> Times:
|
|
255
|
+
return Times(remaining_times=count, unlimited=False)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
Times.unlimited = staticmethod(_times_unlimited)
|
|
259
|
+
Times.exactly = staticmethod(_times_exactly)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass
|
|
263
|
+
class TimeToLive:
|
|
264
|
+
time_unit: str | None = None
|
|
265
|
+
time_to_live: int | None = None
|
|
266
|
+
unlimited: bool | None = None
|
|
267
|
+
|
|
268
|
+
def to_dict(self) -> dict:
|
|
269
|
+
return _strip_none({
|
|
270
|
+
"timeUnit": self.time_unit,
|
|
271
|
+
"timeToLive": self.time_to_live,
|
|
272
|
+
"unlimited": self.unlimited,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
def from_dict(cls, data: dict) -> TimeToLive:
|
|
277
|
+
if data is None:
|
|
278
|
+
return None
|
|
279
|
+
return cls(
|
|
280
|
+
time_unit=data.get("timeUnit"),
|
|
281
|
+
time_to_live=data.get("timeToLive"),
|
|
282
|
+
unlimited=data.get("unlimited"),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _ttl_unlimited() -> TimeToLive:
|
|
287
|
+
return TimeToLive(unlimited=True)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _ttl_exactly(time_to_live: int, time_unit: str) -> TimeToLive:
|
|
291
|
+
return TimeToLive(
|
|
292
|
+
time_unit=time_unit,
|
|
293
|
+
time_to_live=time_to_live,
|
|
294
|
+
unlimited=False,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
TimeToLive.unlimited = staticmethod(_ttl_unlimited)
|
|
299
|
+
TimeToLive.exactly = staticmethod(_ttl_exactly)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@dataclass
|
|
303
|
+
class KeyToMultiValue:
|
|
304
|
+
name: str = ""
|
|
305
|
+
values: list[str] = field(default_factory=list)
|
|
306
|
+
|
|
307
|
+
# name and values are always emitted (not stripped via _strip_none) because
|
|
308
|
+
# the MockServer protocol requires both fields on every header/cookie/parameter.
|
|
309
|
+
def to_dict(self) -> dict:
|
|
310
|
+
return {
|
|
311
|
+
"name": self.name,
|
|
312
|
+
"values": self.values,
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@classmethod
|
|
316
|
+
def from_dict(cls, data: dict) -> KeyToMultiValue:
|
|
317
|
+
if data is None:
|
|
318
|
+
return None
|
|
319
|
+
return cls(
|
|
320
|
+
name=data.get("name", ""),
|
|
321
|
+
values=data.get("values", []),
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@dataclass
|
|
326
|
+
class Body:
|
|
327
|
+
type: str | None = None
|
|
328
|
+
string: str | None = None
|
|
329
|
+
json: Any | None = None
|
|
330
|
+
base64_bytes: str | None = None
|
|
331
|
+
not_body: bool | None = None
|
|
332
|
+
content_type: str | None = None
|
|
333
|
+
charset: str | None = None
|
|
334
|
+
|
|
335
|
+
def to_dict(self) -> dict:
|
|
336
|
+
result = {}
|
|
337
|
+
if self.type is not None:
|
|
338
|
+
result["type"] = self.type
|
|
339
|
+
if self.string is not None:
|
|
340
|
+
result["string"] = self.string
|
|
341
|
+
if self.json is not None:
|
|
342
|
+
result["json"] = self.json
|
|
343
|
+
if self.base64_bytes is not None:
|
|
344
|
+
result["base64Bytes"] = self.base64_bytes
|
|
345
|
+
if self.not_body is not None:
|
|
346
|
+
result["not"] = self.not_body
|
|
347
|
+
if self.content_type is not None:
|
|
348
|
+
result["contentType"] = self.content_type
|
|
349
|
+
if self.charset is not None:
|
|
350
|
+
result["charset"] = self.charset
|
|
351
|
+
return result
|
|
352
|
+
|
|
353
|
+
@classmethod
|
|
354
|
+
def from_dict(cls, data: dict) -> Body:
|
|
355
|
+
if data is None:
|
|
356
|
+
return None
|
|
357
|
+
return cls(
|
|
358
|
+
type=data.get("type"),
|
|
359
|
+
string=data.get("string"),
|
|
360
|
+
json=data.get("json"),
|
|
361
|
+
base64_bytes=data.get("base64Bytes"),
|
|
362
|
+
not_body=data.get("not"),
|
|
363
|
+
content_type=data.get("contentType"),
|
|
364
|
+
charset=data.get("charset"),
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@dataclass
|
|
369
|
+
class JsonRpcBody:
|
|
370
|
+
method: str = ""
|
|
371
|
+
params_schema: str | None = None
|
|
372
|
+
not_body: bool = False
|
|
373
|
+
optional: bool = False
|
|
374
|
+
|
|
375
|
+
def to_dict(self) -> dict:
|
|
376
|
+
result: dict = {"type": "JSON_RPC", "method": self.method}
|
|
377
|
+
if self.params_schema is not None:
|
|
378
|
+
result["paramsSchema"] = self.params_schema
|
|
379
|
+
if self.not_body:
|
|
380
|
+
result["not"] = True
|
|
381
|
+
if self.optional:
|
|
382
|
+
result["optional"] = True
|
|
383
|
+
return result
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
@dataclass
|
|
387
|
+
class GraphQLBody:
|
|
388
|
+
query: str = ""
|
|
389
|
+
operation_name: str | None = None
|
|
390
|
+
variables_schema: str | None = None
|
|
391
|
+
not_body: bool = False
|
|
392
|
+
optional: bool = False
|
|
393
|
+
|
|
394
|
+
def to_dict(self) -> dict:
|
|
395
|
+
result: dict = {"type": "GRAPHQL", "query": self.query}
|
|
396
|
+
if self.operation_name is not None:
|
|
397
|
+
result["operationName"] = self.operation_name
|
|
398
|
+
if self.variables_schema is not None:
|
|
399
|
+
result["variablesSchema"] = self.variables_schema
|
|
400
|
+
if self.not_body:
|
|
401
|
+
result["not"] = True
|
|
402
|
+
if self.optional:
|
|
403
|
+
result["optional"] = True
|
|
404
|
+
return result
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _body_string(value: str) -> Body:
|
|
408
|
+
return Body(type="STRING", string=value)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _body_json(value: Any) -> Body:
|
|
412
|
+
return Body(type="JSON", json=value)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def _body_regex(value: str) -> Body:
|
|
416
|
+
return Body(type="REGEX", string=value)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _body_exact(value: str) -> Body:
|
|
420
|
+
return Body(type="STRING", string=value)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _body_xml(value: str) -> Body:
|
|
424
|
+
return Body(type="XML", string=value)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _body_json_rpc(method: str, params_schema: str | None = None) -> JsonRpcBody:
|
|
428
|
+
return JsonRpcBody(method=method, params_schema=params_schema)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _body_graphql(query: str, operation_name: str | None = None, variables_schema: str | None = None) -> GraphQLBody:
|
|
432
|
+
return GraphQLBody(query=query, operation_name=operation_name, variables_schema=variables_schema)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
Body.string = staticmethod(_body_string)
|
|
436
|
+
Body.json = staticmethod(_body_json)
|
|
437
|
+
Body.regex = staticmethod(_body_regex)
|
|
438
|
+
Body.exact = staticmethod(_body_exact)
|
|
439
|
+
Body.xml = staticmethod(_body_xml)
|
|
440
|
+
Body.json_rpc = staticmethod(_body_json_rpc)
|
|
441
|
+
Body.graphql = staticmethod(_body_graphql)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@dataclass
|
|
445
|
+
class SocketAddress:
|
|
446
|
+
host: str | None = None
|
|
447
|
+
port: int | None = None
|
|
448
|
+
scheme: str | None = None
|
|
449
|
+
|
|
450
|
+
def to_dict(self) -> dict:
|
|
451
|
+
return _strip_none({
|
|
452
|
+
"host": self.host,
|
|
453
|
+
"port": self.port,
|
|
454
|
+
"scheme": self.scheme,
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
@classmethod
|
|
458
|
+
def from_dict(cls, data: dict) -> SocketAddress:
|
|
459
|
+
if data is None:
|
|
460
|
+
return None
|
|
461
|
+
return cls(
|
|
462
|
+
host=data.get("host"),
|
|
463
|
+
port=data.get("port"),
|
|
464
|
+
scheme=data.get("scheme"),
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@dataclass
|
|
469
|
+
class HttpRequest:
|
|
470
|
+
method: str | None = None
|
|
471
|
+
path: str | None = None
|
|
472
|
+
query_string_parameters: list[KeyToMultiValue] | None = None
|
|
473
|
+
headers: list[KeyToMultiValue] | None = None
|
|
474
|
+
cookies: list[KeyToMultiValue] | None = None
|
|
475
|
+
body: Body | str | dict | None = None
|
|
476
|
+
secure: bool | None = None
|
|
477
|
+
keep_alive: bool | None = None
|
|
478
|
+
respond_before_body: bool | None = None
|
|
479
|
+
path_parameters: list[KeyToMultiValue] | None = None
|
|
480
|
+
socket_address: SocketAddress | None = None
|
|
481
|
+
|
|
482
|
+
def to_dict(self) -> dict:
|
|
483
|
+
return _strip_none({
|
|
484
|
+
"method": self.method,
|
|
485
|
+
"path": self.path,
|
|
486
|
+
"queryStringParameters": _serialize_key_multi_values(self.query_string_parameters),
|
|
487
|
+
"headers": _serialize_key_multi_values(self.headers),
|
|
488
|
+
"cookies": _serialize_key_multi_values(self.cookies),
|
|
489
|
+
"body": _serialize_body(self.body),
|
|
490
|
+
"secure": self.secure,
|
|
491
|
+
"keepAlive": self.keep_alive,
|
|
492
|
+
"respondBeforeBody": self.respond_before_body,
|
|
493
|
+
"pathParameters": _serialize_key_multi_values(self.path_parameters),
|
|
494
|
+
"socketAddress": self.socket_address.to_dict() if self.socket_address else None,
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
@classmethod
|
|
498
|
+
def from_dict(cls, data: dict) -> HttpRequest:
|
|
499
|
+
if data is None:
|
|
500
|
+
return None
|
|
501
|
+
return cls(
|
|
502
|
+
method=data.get("method"),
|
|
503
|
+
path=data.get("path"),
|
|
504
|
+
query_string_parameters=_deserialize_key_multi_values(data.get("queryStringParameters")),
|
|
505
|
+
headers=_deserialize_key_multi_values(data.get("headers")),
|
|
506
|
+
cookies=_deserialize_key_multi_values(data.get("cookies")),
|
|
507
|
+
body=_deserialize_body(data.get("body")),
|
|
508
|
+
secure=data.get("secure"),
|
|
509
|
+
keep_alive=data.get("keepAlive"),
|
|
510
|
+
respond_before_body=data.get("respondBeforeBody"),
|
|
511
|
+
path_parameters=_deserialize_key_multi_values(data.get("pathParameters")),
|
|
512
|
+
socket_address=SocketAddress.from_dict(data.get("socketAddress")),
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
@staticmethod
|
|
516
|
+
def request(path: str | None = None) -> HttpRequest:
|
|
517
|
+
return HttpRequest(path=path)
|
|
518
|
+
|
|
519
|
+
def with_method(self, method: str) -> HttpRequest:
|
|
520
|
+
self.method = method
|
|
521
|
+
return self
|
|
522
|
+
|
|
523
|
+
def with_path(self, path: str) -> HttpRequest:
|
|
524
|
+
self.path = path
|
|
525
|
+
return self
|
|
526
|
+
|
|
527
|
+
def with_header(self, name: str, *values: str) -> HttpRequest:
|
|
528
|
+
if self.headers is None:
|
|
529
|
+
self.headers = []
|
|
530
|
+
self.headers.append(KeyToMultiValue(name=name, values=list(values)))
|
|
531
|
+
return self
|
|
532
|
+
|
|
533
|
+
def with_query_param(self, name: str, *values: str) -> HttpRequest:
|
|
534
|
+
if self.query_string_parameters is None:
|
|
535
|
+
self.query_string_parameters = []
|
|
536
|
+
self.query_string_parameters.append(KeyToMultiValue(name=name, values=list(values)))
|
|
537
|
+
return self
|
|
538
|
+
|
|
539
|
+
def with_cookie(self, name: str, value: str) -> HttpRequest:
|
|
540
|
+
if self.cookies is None:
|
|
541
|
+
self.cookies = []
|
|
542
|
+
self.cookies.append(KeyToMultiValue(name=name, values=[value]))
|
|
543
|
+
return self
|
|
544
|
+
|
|
545
|
+
def with_body(self, body: Body | str | dict) -> HttpRequest:
|
|
546
|
+
self.body = body
|
|
547
|
+
return self
|
|
548
|
+
|
|
549
|
+
def with_secure(self, secure: bool) -> HttpRequest:
|
|
550
|
+
self.secure = secure
|
|
551
|
+
return self
|
|
552
|
+
|
|
553
|
+
def with_keep_alive(self, keep_alive: bool) -> HttpRequest:
|
|
554
|
+
self.keep_alive = keep_alive
|
|
555
|
+
return self
|
|
556
|
+
|
|
557
|
+
def with_respond_before_body(self, respond_before_body: bool) -> HttpRequest:
|
|
558
|
+
self.respond_before_body = respond_before_body
|
|
559
|
+
return self
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
@dataclass
|
|
563
|
+
class ConnectionOptions:
|
|
564
|
+
close_socket: bool | None = None
|
|
565
|
+
close_socket_delay: Delay | None = None
|
|
566
|
+
suppress_content_length_header: bool | None = None
|
|
567
|
+
content_length_header_override: int | None = None
|
|
568
|
+
suppress_connection_header: bool | None = None
|
|
569
|
+
chunk_size: int | None = None
|
|
570
|
+
chunk_delay: Delay | None = None
|
|
571
|
+
keep_alive_override: bool | None = None
|
|
572
|
+
|
|
573
|
+
def to_dict(self) -> dict:
|
|
574
|
+
return _strip_none({
|
|
575
|
+
"closeSocket": self.close_socket,
|
|
576
|
+
"closeSocketDelay": self.close_socket_delay.to_dict() if self.close_socket_delay else None,
|
|
577
|
+
"suppressContentLengthHeader": self.suppress_content_length_header,
|
|
578
|
+
"contentLengthHeaderOverride": self.content_length_header_override,
|
|
579
|
+
"suppressConnectionHeader": self.suppress_connection_header,
|
|
580
|
+
"chunkSize": self.chunk_size,
|
|
581
|
+
"chunkDelay": self.chunk_delay.to_dict() if self.chunk_delay else None,
|
|
582
|
+
"keepAliveOverride": self.keep_alive_override,
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
@classmethod
|
|
586
|
+
def from_dict(cls, data: dict) -> ConnectionOptions:
|
|
587
|
+
if data is None:
|
|
588
|
+
return None
|
|
589
|
+
return cls(
|
|
590
|
+
close_socket=data.get("closeSocket"),
|
|
591
|
+
close_socket_delay=Delay.from_dict(data.get("closeSocketDelay")),
|
|
592
|
+
suppress_content_length_header=data.get("suppressContentLengthHeader"),
|
|
593
|
+
content_length_header_override=data.get("contentLengthHeaderOverride"),
|
|
594
|
+
suppress_connection_header=data.get("suppressConnectionHeader"),
|
|
595
|
+
chunk_size=data.get("chunkSize"),
|
|
596
|
+
chunk_delay=Delay.from_dict(data.get("chunkDelay")),
|
|
597
|
+
keep_alive_override=data.get("keepAliveOverride"),
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
@dataclass
|
|
602
|
+
class HttpResponse:
|
|
603
|
+
status_code: int | None = None
|
|
604
|
+
reason_phrase: str | None = None
|
|
605
|
+
headers: list[KeyToMultiValue] | None = None
|
|
606
|
+
cookies: list[KeyToMultiValue] | None = None
|
|
607
|
+
body: Body | str | dict | None = None
|
|
608
|
+
delay: Delay | None = None
|
|
609
|
+
connection_options: ConnectionOptions | None = None
|
|
610
|
+
primary: bool | None = None
|
|
611
|
+
|
|
612
|
+
def to_dict(self) -> dict:
|
|
613
|
+
return _strip_none({
|
|
614
|
+
"statusCode": self.status_code,
|
|
615
|
+
"reasonPhrase": self.reason_phrase,
|
|
616
|
+
"headers": _serialize_key_multi_values(self.headers),
|
|
617
|
+
"cookies": _serialize_key_multi_values(self.cookies),
|
|
618
|
+
"body": _serialize_body(self.body),
|
|
619
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
620
|
+
"connectionOptions": self.connection_options.to_dict() if self.connection_options else None,
|
|
621
|
+
"primary": self.primary,
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
@classmethod
|
|
625
|
+
def from_dict(cls, data: dict) -> HttpResponse:
|
|
626
|
+
if data is None:
|
|
627
|
+
return None
|
|
628
|
+
return cls(
|
|
629
|
+
status_code=data.get("statusCode"),
|
|
630
|
+
reason_phrase=data.get("reasonPhrase"),
|
|
631
|
+
headers=_deserialize_key_multi_values(data.get("headers")),
|
|
632
|
+
cookies=_deserialize_key_multi_values(data.get("cookies")),
|
|
633
|
+
body=_deserialize_body(data.get("body")),
|
|
634
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
635
|
+
connection_options=ConnectionOptions.from_dict(data.get("connectionOptions")),
|
|
636
|
+
primary=data.get("primary"),
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
@staticmethod
|
|
640
|
+
def response(body: str | None = None, status_code: int | None = None) -> HttpResponse:
|
|
641
|
+
resp = HttpResponse()
|
|
642
|
+
if body is not None:
|
|
643
|
+
resp.body = body
|
|
644
|
+
if status_code is None:
|
|
645
|
+
resp.status_code = 200
|
|
646
|
+
resp.reason_phrase = "OK"
|
|
647
|
+
else:
|
|
648
|
+
resp.status_code = status_code
|
|
649
|
+
elif status_code is not None:
|
|
650
|
+
resp.status_code = status_code
|
|
651
|
+
return resp
|
|
652
|
+
|
|
653
|
+
@staticmethod
|
|
654
|
+
def not_found_response() -> HttpResponse:
|
|
655
|
+
return HttpResponse(status_code=404, reason_phrase="Not Found")
|
|
656
|
+
|
|
657
|
+
def with_status_code(self, status_code: int) -> HttpResponse:
|
|
658
|
+
self.status_code = status_code
|
|
659
|
+
return self
|
|
660
|
+
|
|
661
|
+
def with_header(self, name: str, *values: str) -> HttpResponse:
|
|
662
|
+
if self.headers is None:
|
|
663
|
+
self.headers = []
|
|
664
|
+
self.headers.append(KeyToMultiValue(name=name, values=list(values)))
|
|
665
|
+
return self
|
|
666
|
+
|
|
667
|
+
def with_cookie(self, name: str, value: str) -> HttpResponse:
|
|
668
|
+
if self.cookies is None:
|
|
669
|
+
self.cookies = []
|
|
670
|
+
self.cookies.append(KeyToMultiValue(name=name, values=[value]))
|
|
671
|
+
return self
|
|
672
|
+
|
|
673
|
+
def with_body(self, body: Body | str | dict) -> HttpResponse:
|
|
674
|
+
self.body = body
|
|
675
|
+
return self
|
|
676
|
+
|
|
677
|
+
def with_delay(self, delay: Delay) -> HttpResponse:
|
|
678
|
+
self.delay = delay
|
|
679
|
+
return self
|
|
680
|
+
|
|
681
|
+
def with_reason_phrase(self, reason_phrase: str) -> HttpResponse:
|
|
682
|
+
self.reason_phrase = reason_phrase
|
|
683
|
+
return self
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
@dataclass
|
|
687
|
+
class HttpForward:
|
|
688
|
+
host: str | None = None
|
|
689
|
+
port: int | None = None
|
|
690
|
+
scheme: str | None = None
|
|
691
|
+
delay: Delay | None = None
|
|
692
|
+
primary: bool | None = None
|
|
693
|
+
|
|
694
|
+
def to_dict(self) -> dict:
|
|
695
|
+
return _strip_none({
|
|
696
|
+
"host": self.host,
|
|
697
|
+
"port": self.port,
|
|
698
|
+
"scheme": self.scheme,
|
|
699
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
700
|
+
"primary": self.primary,
|
|
701
|
+
})
|
|
702
|
+
|
|
703
|
+
@classmethod
|
|
704
|
+
def from_dict(cls, data: dict) -> HttpForward:
|
|
705
|
+
if data is None:
|
|
706
|
+
return None
|
|
707
|
+
return cls(
|
|
708
|
+
host=data.get("host"),
|
|
709
|
+
port=data.get("port"),
|
|
710
|
+
scheme=data.get("scheme"),
|
|
711
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
712
|
+
primary=data.get("primary"),
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
@staticmethod
|
|
716
|
+
def forward() -> HttpForward:
|
|
717
|
+
return HttpForward()
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@dataclass
|
|
721
|
+
class HttpTemplate:
|
|
722
|
+
template_type: str = "JAVASCRIPT"
|
|
723
|
+
template: str | None = None
|
|
724
|
+
delay: Delay | None = None
|
|
725
|
+
primary: bool | None = None
|
|
726
|
+
|
|
727
|
+
def to_dict(self) -> dict:
|
|
728
|
+
return _strip_none({
|
|
729
|
+
"templateType": self.template_type,
|
|
730
|
+
"template": self.template,
|
|
731
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
732
|
+
"primary": self.primary,
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
@classmethod
|
|
736
|
+
def from_dict(cls, data: dict) -> HttpTemplate:
|
|
737
|
+
if data is None:
|
|
738
|
+
return None
|
|
739
|
+
return cls(
|
|
740
|
+
template_type=data.get("templateType", "JAVASCRIPT"),
|
|
741
|
+
template=data.get("template"),
|
|
742
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
743
|
+
primary=data.get("primary"),
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def _http_template_factory(template_type: str, template: str | None = None) -> HttpTemplate:
|
|
748
|
+
return HttpTemplate(template_type=template_type, template=template)
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
HttpTemplate.template = staticmethod(_http_template_factory)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
@dataclass
|
|
755
|
+
class HttpClassCallback:
|
|
756
|
+
callback_class: str | None = None
|
|
757
|
+
delay: Delay | None = None
|
|
758
|
+
primary: bool | None = None
|
|
759
|
+
|
|
760
|
+
def to_dict(self) -> dict:
|
|
761
|
+
return _strip_none({
|
|
762
|
+
"callbackClass": self.callback_class,
|
|
763
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
764
|
+
"primary": self.primary,
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
@classmethod
|
|
768
|
+
def from_dict(cls, data: dict) -> HttpClassCallback:
|
|
769
|
+
if data is None:
|
|
770
|
+
return None
|
|
771
|
+
return cls(
|
|
772
|
+
callback_class=data.get("callbackClass"),
|
|
773
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
774
|
+
primary=data.get("primary"),
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
@staticmethod
|
|
778
|
+
def callback(callback_class: str | None = None) -> HttpClassCallback:
|
|
779
|
+
return HttpClassCallback(callback_class=callback_class)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
@dataclass
|
|
783
|
+
class HttpObjectCallback:
|
|
784
|
+
client_id: str | None = None
|
|
785
|
+
response_callback: bool | None = None
|
|
786
|
+
delay: Delay | None = None
|
|
787
|
+
primary: bool | None = None
|
|
788
|
+
|
|
789
|
+
def to_dict(self) -> dict:
|
|
790
|
+
return _strip_none({
|
|
791
|
+
"clientId": self.client_id,
|
|
792
|
+
"responseCallback": self.response_callback,
|
|
793
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
794
|
+
"primary": self.primary,
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
@classmethod
|
|
798
|
+
def from_dict(cls, data: dict) -> HttpObjectCallback:
|
|
799
|
+
if data is None:
|
|
800
|
+
return None
|
|
801
|
+
return cls(
|
|
802
|
+
client_id=data.get("clientId"),
|
|
803
|
+
response_callback=data.get("responseCallback"),
|
|
804
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
805
|
+
primary=data.get("primary"),
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
@dataclass
|
|
810
|
+
class HttpError:
|
|
811
|
+
drop_connection: bool | None = None
|
|
812
|
+
response_bytes: str | None = None
|
|
813
|
+
delay: Delay | None = None
|
|
814
|
+
primary: bool | None = None
|
|
815
|
+
|
|
816
|
+
def to_dict(self) -> dict:
|
|
817
|
+
return _strip_none({
|
|
818
|
+
"dropConnection": self.drop_connection,
|
|
819
|
+
"responseBytes": self.response_bytes,
|
|
820
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
821
|
+
"primary": self.primary,
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
@classmethod
|
|
825
|
+
def from_dict(cls, data: dict) -> HttpError:
|
|
826
|
+
if data is None:
|
|
827
|
+
return None
|
|
828
|
+
return cls(
|
|
829
|
+
drop_connection=data.get("dropConnection"),
|
|
830
|
+
response_bytes=data.get("responseBytes"),
|
|
831
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
832
|
+
primary=data.get("primary"),
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
@staticmethod
|
|
836
|
+
def error() -> HttpError:
|
|
837
|
+
return HttpError()
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
@dataclass
|
|
841
|
+
class SseEvent:
|
|
842
|
+
event: str | None = None
|
|
843
|
+
data: str | None = None
|
|
844
|
+
id: str | None = None
|
|
845
|
+
retry: int | None = None
|
|
846
|
+
delay: Delay | None = None
|
|
847
|
+
|
|
848
|
+
def to_dict(self) -> dict:
|
|
849
|
+
result: dict = {}
|
|
850
|
+
if self.event is not None:
|
|
851
|
+
result["event"] = self.event
|
|
852
|
+
if self.data is not None:
|
|
853
|
+
result["data"] = self.data
|
|
854
|
+
if self.id is not None:
|
|
855
|
+
result["id"] = self.id
|
|
856
|
+
if self.retry is not None:
|
|
857
|
+
result["retry"] = self.retry
|
|
858
|
+
if self.delay is not None:
|
|
859
|
+
result["delay"] = self.delay.to_dict()
|
|
860
|
+
return result
|
|
861
|
+
|
|
862
|
+
@classmethod
|
|
863
|
+
def from_dict(cls, data: dict) -> SseEvent:
|
|
864
|
+
if data is None:
|
|
865
|
+
return None
|
|
866
|
+
return cls(
|
|
867
|
+
event=data.get("event"),
|
|
868
|
+
data=data.get("data"),
|
|
869
|
+
id=data.get("id"),
|
|
870
|
+
retry=data.get("retry"),
|
|
871
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
@dataclass
|
|
876
|
+
class HttpSseResponse:
|
|
877
|
+
status_code: int | None = None
|
|
878
|
+
headers: list[KeyToMultiValue] | None = None
|
|
879
|
+
events: list[SseEvent] | None = None
|
|
880
|
+
close_connection: bool | None = None
|
|
881
|
+
delay: Delay | None = None
|
|
882
|
+
primary: bool | None = None
|
|
883
|
+
|
|
884
|
+
def to_dict(self) -> dict:
|
|
885
|
+
result: dict = {}
|
|
886
|
+
if self.status_code is not None:
|
|
887
|
+
result["statusCode"] = self.status_code
|
|
888
|
+
if self.headers is not None:
|
|
889
|
+
result["headers"] = _serialize_key_multi_values(self.headers)
|
|
890
|
+
if self.events is not None:
|
|
891
|
+
result["events"] = [e.to_dict() if hasattr(e, 'to_dict') else e for e in self.events]
|
|
892
|
+
if self.close_connection is not None:
|
|
893
|
+
result["closeConnection"] = self.close_connection
|
|
894
|
+
if self.delay is not None:
|
|
895
|
+
result["delay"] = self.delay.to_dict()
|
|
896
|
+
if self.primary is not None:
|
|
897
|
+
result["primary"] = self.primary
|
|
898
|
+
return result
|
|
899
|
+
|
|
900
|
+
@classmethod
|
|
901
|
+
def from_dict(cls, data: dict) -> HttpSseResponse:
|
|
902
|
+
if data is None:
|
|
903
|
+
return None
|
|
904
|
+
events_data = data.get("events")
|
|
905
|
+
events = None
|
|
906
|
+
if events_data is not None:
|
|
907
|
+
events = [SseEvent.from_dict(e) if isinstance(e, dict) else e for e in events_data]
|
|
908
|
+
return cls(
|
|
909
|
+
status_code=data.get("statusCode"),
|
|
910
|
+
headers=_deserialize_key_multi_values(data.get("headers")),
|
|
911
|
+
events=events,
|
|
912
|
+
close_connection=data.get("closeConnection"),
|
|
913
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
914
|
+
primary=data.get("primary"),
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
@dataclass
|
|
919
|
+
class WebSocketMessage:
|
|
920
|
+
text: str | None = None
|
|
921
|
+
binary: bytes | None = None
|
|
922
|
+
delay: Delay | None = None
|
|
923
|
+
|
|
924
|
+
def to_dict(self) -> dict:
|
|
925
|
+
result: dict = {}
|
|
926
|
+
if self.text is not None:
|
|
927
|
+
result["text"] = self.text
|
|
928
|
+
if self.binary is not None:
|
|
929
|
+
import base64
|
|
930
|
+
result["binary"] = base64.b64encode(self.binary).decode("utf-8")
|
|
931
|
+
if self.delay is not None:
|
|
932
|
+
result["delay"] = self.delay.to_dict()
|
|
933
|
+
return result
|
|
934
|
+
|
|
935
|
+
@classmethod
|
|
936
|
+
def from_dict(cls, data: dict) -> WebSocketMessage:
|
|
937
|
+
if data is None:
|
|
938
|
+
return None
|
|
939
|
+
import base64
|
|
940
|
+
binary_data = data.get("binary")
|
|
941
|
+
binary = base64.b64decode(binary_data) if binary_data is not None else None
|
|
942
|
+
return cls(
|
|
943
|
+
text=data.get("text"),
|
|
944
|
+
binary=binary,
|
|
945
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
946
|
+
)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
@dataclass
|
|
950
|
+
class HttpWebSocketResponse:
|
|
951
|
+
subprotocol: str | None = None
|
|
952
|
+
messages: list[WebSocketMessage] | None = None
|
|
953
|
+
close_connection: bool | None = None
|
|
954
|
+
delay: Delay | None = None
|
|
955
|
+
primary: bool | None = None
|
|
956
|
+
|
|
957
|
+
def to_dict(self) -> dict:
|
|
958
|
+
result: dict = {}
|
|
959
|
+
if self.subprotocol is not None:
|
|
960
|
+
result["subprotocol"] = self.subprotocol
|
|
961
|
+
if self.messages is not None:
|
|
962
|
+
result["messages"] = [m.to_dict() if hasattr(m, 'to_dict') else m for m in self.messages]
|
|
963
|
+
if self.close_connection is not None:
|
|
964
|
+
result["closeConnection"] = self.close_connection
|
|
965
|
+
if self.delay is not None:
|
|
966
|
+
result["delay"] = self.delay.to_dict()
|
|
967
|
+
if self.primary is not None:
|
|
968
|
+
result["primary"] = self.primary
|
|
969
|
+
return result
|
|
970
|
+
|
|
971
|
+
@classmethod
|
|
972
|
+
def from_dict(cls, data: dict) -> HttpWebSocketResponse:
|
|
973
|
+
if data is None:
|
|
974
|
+
return None
|
|
975
|
+
messages_data = data.get("messages")
|
|
976
|
+
messages = None
|
|
977
|
+
if messages_data is not None:
|
|
978
|
+
messages = [WebSocketMessage.from_dict(m) if isinstance(m, dict) else m for m in messages_data]
|
|
979
|
+
return cls(
|
|
980
|
+
subprotocol=data.get("subprotocol"),
|
|
981
|
+
messages=messages,
|
|
982
|
+
close_connection=data.get("closeConnection"),
|
|
983
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
984
|
+
primary=data.get("primary"),
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
@dataclass
|
|
989
|
+
class HttpOverrideForwardedRequest:
|
|
990
|
+
http_request: HttpRequest | None = None
|
|
991
|
+
http_response: HttpResponse | None = None
|
|
992
|
+
delay: Delay | None = None
|
|
993
|
+
request_modifier: dict | None = None
|
|
994
|
+
response_modifier: dict | None = None
|
|
995
|
+
primary: bool | None = None
|
|
996
|
+
|
|
997
|
+
def to_dict(self) -> dict:
|
|
998
|
+
return _strip_none({
|
|
999
|
+
"httpRequest": self.http_request.to_dict() if self.http_request else None,
|
|
1000
|
+
"httpResponse": self.http_response.to_dict() if self.http_response else None,
|
|
1001
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
1002
|
+
"requestModifier": self.request_modifier,
|
|
1003
|
+
"responseModifier": self.response_modifier,
|
|
1004
|
+
"primary": self.primary,
|
|
1005
|
+
})
|
|
1006
|
+
|
|
1007
|
+
@classmethod
|
|
1008
|
+
def from_dict(cls, data: dict) -> HttpOverrideForwardedRequest:
|
|
1009
|
+
if data is None:
|
|
1010
|
+
return None
|
|
1011
|
+
return cls(
|
|
1012
|
+
http_request=HttpRequest.from_dict(data.get("httpRequest")),
|
|
1013
|
+
http_response=HttpResponse.from_dict(data.get("httpResponse")),
|
|
1014
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
1015
|
+
request_modifier=data.get("requestModifier"),
|
|
1016
|
+
response_modifier=data.get("responseModifier"),
|
|
1017
|
+
primary=data.get("primary"),
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
@staticmethod
|
|
1021
|
+
def forward_overridden_request(request: HttpRequest | None = None) -> HttpOverrideForwardedRequest:
|
|
1022
|
+
return HttpOverrideForwardedRequest(http_request=request)
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
@dataclass
|
|
1026
|
+
class HttpRequestAndHttpResponse:
|
|
1027
|
+
http_request: HttpRequest | None = None
|
|
1028
|
+
http_response: HttpResponse | None = None
|
|
1029
|
+
|
|
1030
|
+
def to_dict(self) -> dict:
|
|
1031
|
+
return _strip_none({
|
|
1032
|
+
"httpRequest": self.http_request.to_dict() if self.http_request else None,
|
|
1033
|
+
"httpResponse": self.http_response.to_dict() if self.http_response else None,
|
|
1034
|
+
})
|
|
1035
|
+
|
|
1036
|
+
@classmethod
|
|
1037
|
+
def from_dict(cls, data: dict) -> HttpRequestAndHttpResponse:
|
|
1038
|
+
if data is None:
|
|
1039
|
+
return None
|
|
1040
|
+
return cls(
|
|
1041
|
+
http_request=HttpRequest.from_dict(data.get("httpRequest")),
|
|
1042
|
+
http_response=HttpResponse.from_dict(data.get("httpResponse")),
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
@dataclass
|
|
1047
|
+
class ExpectationId:
|
|
1048
|
+
id: str = ""
|
|
1049
|
+
|
|
1050
|
+
def to_dict(self) -> dict:
|
|
1051
|
+
return {"id": self.id}
|
|
1052
|
+
|
|
1053
|
+
@classmethod
|
|
1054
|
+
def from_dict(cls, data: dict) -> ExpectationId:
|
|
1055
|
+
if data is None:
|
|
1056
|
+
return None
|
|
1057
|
+
return cls(id=data.get("id", ""))
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
@dataclass
|
|
1061
|
+
class AfterAction:
|
|
1062
|
+
http_request: HttpRequest | None = None
|
|
1063
|
+
http_class_callback: HttpClassCallback | None = None
|
|
1064
|
+
http_object_callback: HttpObjectCallback | None = None
|
|
1065
|
+
delay: Delay | None = None
|
|
1066
|
+
|
|
1067
|
+
def to_dict(self) -> dict:
|
|
1068
|
+
return _strip_none({
|
|
1069
|
+
"httpRequest": self.http_request.to_dict() if self.http_request else None,
|
|
1070
|
+
"httpClassCallback": self.http_class_callback.to_dict() if self.http_class_callback else None,
|
|
1071
|
+
"httpObjectCallback": self.http_object_callback.to_dict() if self.http_object_callback else None,
|
|
1072
|
+
"delay": self.delay.to_dict() if self.delay else None,
|
|
1073
|
+
})
|
|
1074
|
+
|
|
1075
|
+
@classmethod
|
|
1076
|
+
def from_dict(cls, data: dict) -> AfterAction:
|
|
1077
|
+
if data is None:
|
|
1078
|
+
return None
|
|
1079
|
+
return cls(
|
|
1080
|
+
http_request=HttpRequest.from_dict(data.get("httpRequest")),
|
|
1081
|
+
http_class_callback=HttpClassCallback.from_dict(data.get("httpClassCallback")),
|
|
1082
|
+
http_object_callback=HttpObjectCallback.from_dict(data.get("httpObjectCallback")),
|
|
1083
|
+
delay=Delay.from_dict(data.get("delay")),
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
@dataclass
|
|
1088
|
+
class Expectation:
|
|
1089
|
+
id: str | None = None
|
|
1090
|
+
priority: int | None = None
|
|
1091
|
+
percentage: int | None = None
|
|
1092
|
+
http_request: HttpRequest | None = None
|
|
1093
|
+
http_response: HttpResponse | None = None
|
|
1094
|
+
http_response_template: HttpTemplate | None = None
|
|
1095
|
+
http_response_class_callback: HttpClassCallback | None = None
|
|
1096
|
+
http_response_object_callback: HttpObjectCallback | None = None
|
|
1097
|
+
http_forward: HttpForward | None = None
|
|
1098
|
+
http_forward_template: HttpTemplate | None = None
|
|
1099
|
+
http_forward_class_callback: HttpClassCallback | None = None
|
|
1100
|
+
http_forward_object_callback: HttpObjectCallback | None = None
|
|
1101
|
+
http_override_forwarded_request: HttpOverrideForwardedRequest | None = None
|
|
1102
|
+
http_error: HttpError | None = None
|
|
1103
|
+
http_sse_response: HttpSseResponse | None = None
|
|
1104
|
+
http_websocket_response: HttpWebSocketResponse | None = None
|
|
1105
|
+
times: Times | None = None
|
|
1106
|
+
time_to_live: TimeToLive | None = None
|
|
1107
|
+
after_actions: list[AfterAction] | None = None
|
|
1108
|
+
http_responses: list[HttpResponse] | None = None
|
|
1109
|
+
response_mode: str | None = None
|
|
1110
|
+
scenario_name: str | None = None
|
|
1111
|
+
scenario_state: str | None = None
|
|
1112
|
+
new_scenario_state: str | None = None
|
|
1113
|
+
|
|
1114
|
+
def to_dict(self) -> dict:
|
|
1115
|
+
return _strip_none({
|
|
1116
|
+
"id": self.id,
|
|
1117
|
+
"priority": self.priority,
|
|
1118
|
+
"percentage": self.percentage,
|
|
1119
|
+
"httpRequest": self.http_request.to_dict() if self.http_request else None,
|
|
1120
|
+
"httpResponse": self.http_response.to_dict() if self.http_response else None,
|
|
1121
|
+
"httpResponseTemplate": self.http_response_template.to_dict() if self.http_response_template else None,
|
|
1122
|
+
"httpResponseClassCallback": self.http_response_class_callback.to_dict() if self.http_response_class_callback else None,
|
|
1123
|
+
"httpResponseObjectCallback": self.http_response_object_callback.to_dict() if self.http_response_object_callback else None,
|
|
1124
|
+
"httpForward": self.http_forward.to_dict() if self.http_forward else None,
|
|
1125
|
+
"httpForwardTemplate": self.http_forward_template.to_dict() if self.http_forward_template else None,
|
|
1126
|
+
"httpForwardClassCallback": self.http_forward_class_callback.to_dict() if self.http_forward_class_callback else None,
|
|
1127
|
+
"httpForwardObjectCallback": self.http_forward_object_callback.to_dict() if self.http_forward_object_callback else None,
|
|
1128
|
+
"httpOverrideForwardedRequest": self.http_override_forwarded_request.to_dict() if self.http_override_forwarded_request else None,
|
|
1129
|
+
"httpError": self.http_error.to_dict() if self.http_error else None,
|
|
1130
|
+
"httpSseResponse": self.http_sse_response.to_dict() if self.http_sse_response else None,
|
|
1131
|
+
"httpWebSocketResponse": self.http_websocket_response.to_dict() if self.http_websocket_response else None,
|
|
1132
|
+
"times": self.times.to_dict() if self.times else None,
|
|
1133
|
+
"timeToLive": self.time_to_live.to_dict() if self.time_to_live else None,
|
|
1134
|
+
"afterActions": [a.to_dict() for a in self.after_actions] if self.after_actions else None,
|
|
1135
|
+
"httpResponses": [r.to_dict() for r in self.http_responses] if self.http_responses else None,
|
|
1136
|
+
"responseMode": self.response_mode,
|
|
1137
|
+
"scenarioName": self.scenario_name,
|
|
1138
|
+
"scenarioState": self.scenario_state,
|
|
1139
|
+
"newScenarioState": self.new_scenario_state,
|
|
1140
|
+
})
|
|
1141
|
+
|
|
1142
|
+
@classmethod
|
|
1143
|
+
def from_dict(cls, data: dict) -> Expectation:
|
|
1144
|
+
if data is None:
|
|
1145
|
+
return None
|
|
1146
|
+
after_actions_data = data.get("afterActions")
|
|
1147
|
+
if isinstance(after_actions_data, dict):
|
|
1148
|
+
after_actions_data = [after_actions_data]
|
|
1149
|
+
return cls(
|
|
1150
|
+
id=data.get("id"),
|
|
1151
|
+
priority=data.get("priority"),
|
|
1152
|
+
percentage=data.get("percentage"),
|
|
1153
|
+
http_request=HttpRequest.from_dict(data.get("httpRequest")),
|
|
1154
|
+
http_response=HttpResponse.from_dict(data.get("httpResponse")),
|
|
1155
|
+
http_response_template=HttpTemplate.from_dict(data.get("httpResponseTemplate")),
|
|
1156
|
+
http_response_class_callback=HttpClassCallback.from_dict(data.get("httpResponseClassCallback")),
|
|
1157
|
+
http_response_object_callback=HttpObjectCallback.from_dict(data.get("httpResponseObjectCallback")),
|
|
1158
|
+
http_forward=HttpForward.from_dict(data.get("httpForward")),
|
|
1159
|
+
http_forward_template=HttpTemplate.from_dict(data.get("httpForwardTemplate")),
|
|
1160
|
+
http_forward_class_callback=HttpClassCallback.from_dict(data.get("httpForwardClassCallback")),
|
|
1161
|
+
http_forward_object_callback=HttpObjectCallback.from_dict(data.get("httpForwardObjectCallback")),
|
|
1162
|
+
http_override_forwarded_request=HttpOverrideForwardedRequest.from_dict(data.get("httpOverrideForwardedRequest")),
|
|
1163
|
+
http_error=HttpError.from_dict(data.get("httpError")),
|
|
1164
|
+
http_sse_response=HttpSseResponse.from_dict(data.get("httpSseResponse")),
|
|
1165
|
+
http_websocket_response=HttpWebSocketResponse.from_dict(data.get("httpWebSocketResponse")),
|
|
1166
|
+
times=Times.from_dict(data.get("times")),
|
|
1167
|
+
time_to_live=TimeToLive.from_dict(data.get("timeToLive")),
|
|
1168
|
+
after_actions=[AfterAction.from_dict(a) for a in after_actions_data] if after_actions_data else None,
|
|
1169
|
+
http_responses=[HttpResponse.from_dict(r) for r in data["httpResponses"]] if data.get("httpResponses") else None,
|
|
1170
|
+
response_mode=data.get("responseMode"),
|
|
1171
|
+
scenario_name=data.get("scenarioName"),
|
|
1172
|
+
scenario_state=data.get("scenarioState"),
|
|
1173
|
+
new_scenario_state=data.get("newScenarioState"),
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
@dataclass
|
|
1178
|
+
class OpenAPIDefinition:
|
|
1179
|
+
spec_url_or_payload: str | None = None
|
|
1180
|
+
operation_id: str | None = None
|
|
1181
|
+
|
|
1182
|
+
def to_dict(self) -> dict:
|
|
1183
|
+
return _strip_none({
|
|
1184
|
+
"specUrlOrPayload": self.spec_url_or_payload,
|
|
1185
|
+
"operationId": self.operation_id,
|
|
1186
|
+
})
|
|
1187
|
+
|
|
1188
|
+
@classmethod
|
|
1189
|
+
def from_dict(cls, data: dict) -> OpenAPIDefinition:
|
|
1190
|
+
if data is None:
|
|
1191
|
+
return None
|
|
1192
|
+
return cls(
|
|
1193
|
+
spec_url_or_payload=data.get("specUrlOrPayload"),
|
|
1194
|
+
operation_id=data.get("operationId"),
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
@dataclass
|
|
1199
|
+
class OpenAPIExpectation:
|
|
1200
|
+
spec_url_or_payload: str | None = None
|
|
1201
|
+
operations_and_responses: dict | None = None
|
|
1202
|
+
|
|
1203
|
+
def to_dict(self) -> dict:
|
|
1204
|
+
return _strip_none({
|
|
1205
|
+
"specUrlOrPayload": self.spec_url_or_payload,
|
|
1206
|
+
"operationsAndResponses": self.operations_and_responses,
|
|
1207
|
+
})
|
|
1208
|
+
|
|
1209
|
+
@classmethod
|
|
1210
|
+
def from_dict(cls, data: dict) -> OpenAPIExpectation:
|
|
1211
|
+
if data is None:
|
|
1212
|
+
return None
|
|
1213
|
+
return cls(
|
|
1214
|
+
spec_url_or_payload=data.get("specUrlOrPayload"),
|
|
1215
|
+
operations_and_responses=data.get("operationsAndResponses"),
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
@dataclass
|
|
1220
|
+
class VerificationTimes:
|
|
1221
|
+
at_least: int | None = None
|
|
1222
|
+
at_most: int | None = None
|
|
1223
|
+
|
|
1224
|
+
def to_dict(self) -> dict:
|
|
1225
|
+
return _strip_none({
|
|
1226
|
+
"atLeast": self.at_least,
|
|
1227
|
+
"atMost": self.at_most,
|
|
1228
|
+
})
|
|
1229
|
+
|
|
1230
|
+
@classmethod
|
|
1231
|
+
def from_dict(cls, data: dict) -> VerificationTimes:
|
|
1232
|
+
if data is None:
|
|
1233
|
+
return None
|
|
1234
|
+
return cls(
|
|
1235
|
+
at_least=data.get("atLeast"),
|
|
1236
|
+
at_most=data.get("atMost"),
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
|
|
1240
|
+
def _vt_at_least(count: int) -> VerificationTimes:
|
|
1241
|
+
return VerificationTimes(at_least=count)
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
def _vt_at_most(count: int) -> VerificationTimes:
|
|
1245
|
+
return VerificationTimes(at_most=count)
|
|
1246
|
+
|
|
1247
|
+
|
|
1248
|
+
def _vt_exactly(count: int) -> VerificationTimes:
|
|
1249
|
+
return VerificationTimes(at_least=count, at_most=count)
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
def _vt_once() -> VerificationTimes:
|
|
1253
|
+
return VerificationTimes(at_least=1, at_most=1)
|
|
1254
|
+
|
|
1255
|
+
|
|
1256
|
+
def _vt_between(at_least: int, at_most: int) -> VerificationTimes:
|
|
1257
|
+
return VerificationTimes(at_least=at_least, at_most=at_most)
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
VerificationTimes.at_least = staticmethod(_vt_at_least)
|
|
1261
|
+
VerificationTimes.at_most = staticmethod(_vt_at_most)
|
|
1262
|
+
VerificationTimes.exactly = staticmethod(_vt_exactly)
|
|
1263
|
+
VerificationTimes.once = staticmethod(_vt_once)
|
|
1264
|
+
VerificationTimes.between = staticmethod(_vt_between)
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
@dataclass
|
|
1268
|
+
class Verification:
|
|
1269
|
+
http_request: HttpRequest | None = None
|
|
1270
|
+
expectation_id: ExpectationId | None = None
|
|
1271
|
+
times: VerificationTimes | None = None
|
|
1272
|
+
maximum_number_of_request_to_return_in_verification_failure: int | None = None
|
|
1273
|
+
|
|
1274
|
+
def to_dict(self) -> dict:
|
|
1275
|
+
return _strip_none({
|
|
1276
|
+
"httpRequest": self.http_request.to_dict() if self.http_request else None,
|
|
1277
|
+
"expectationId": self.expectation_id.to_dict() if self.expectation_id else None,
|
|
1278
|
+
"times": self.times.to_dict() if self.times else None,
|
|
1279
|
+
"maximumNumberOfRequestToReturnInVerificationFailure": self.maximum_number_of_request_to_return_in_verification_failure,
|
|
1280
|
+
})
|
|
1281
|
+
|
|
1282
|
+
@classmethod
|
|
1283
|
+
def from_dict(cls, data: dict) -> Verification:
|
|
1284
|
+
if data is None:
|
|
1285
|
+
return None
|
|
1286
|
+
return cls(
|
|
1287
|
+
http_request=HttpRequest.from_dict(data.get("httpRequest")),
|
|
1288
|
+
expectation_id=ExpectationId.from_dict(data.get("expectationId")),
|
|
1289
|
+
times=VerificationTimes.from_dict(data.get("times")),
|
|
1290
|
+
maximum_number_of_request_to_return_in_verification_failure=data.get("maximumNumberOfRequestToReturnInVerificationFailure"),
|
|
1291
|
+
)
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
@dataclass
|
|
1295
|
+
class VerificationSequence:
|
|
1296
|
+
http_requests: list[HttpRequest] | None = None
|
|
1297
|
+
expectation_ids: list[ExpectationId] | None = None
|
|
1298
|
+
|
|
1299
|
+
def to_dict(self) -> dict:
|
|
1300
|
+
return _strip_none({
|
|
1301
|
+
"httpRequests": [r.to_dict() for r in self.http_requests] if self.http_requests else None,
|
|
1302
|
+
"expectationIds": [e.to_dict() for e in self.expectation_ids] if self.expectation_ids else None,
|
|
1303
|
+
})
|
|
1304
|
+
|
|
1305
|
+
@classmethod
|
|
1306
|
+
def from_dict(cls, data: dict) -> VerificationSequence:
|
|
1307
|
+
if data is None:
|
|
1308
|
+
return None
|
|
1309
|
+
http_requests_data = data.get("httpRequests")
|
|
1310
|
+
expectation_ids_data = data.get("expectationIds")
|
|
1311
|
+
return cls(
|
|
1312
|
+
http_requests=[HttpRequest.from_dict(r) for r in http_requests_data] if http_requests_data else None,
|
|
1313
|
+
expectation_ids=[ExpectationId.from_dict(e) for e in expectation_ids_data] if expectation_ids_data else None,
|
|
1314
|
+
)
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
@dataclass
|
|
1318
|
+
class Ports:
|
|
1319
|
+
ports: list[int] = field(default_factory=list)
|
|
1320
|
+
|
|
1321
|
+
def to_dict(self) -> dict:
|
|
1322
|
+
return {"ports": self.ports}
|
|
1323
|
+
|
|
1324
|
+
@classmethod
|
|
1325
|
+
def from_dict(cls, data: dict) -> Ports:
|
|
1326
|
+
if data is None:
|
|
1327
|
+
return None
|
|
1328
|
+
return cls(ports=data.get("ports", []))
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
@dataclass
|
|
1332
|
+
class CrudExpectationsDefinition:
|
|
1333
|
+
base_path: str = ""
|
|
1334
|
+
id_field: str = "id"
|
|
1335
|
+
id_strategy: str = "AUTO_INCREMENT"
|
|
1336
|
+
initial_data: list[dict] | None = None
|
|
1337
|
+
|
|
1338
|
+
def to_dict(self) -> dict:
|
|
1339
|
+
return _strip_none({
|
|
1340
|
+
"basePath": self.base_path,
|
|
1341
|
+
"idField": self.id_field,
|
|
1342
|
+
"idStrategy": self.id_strategy,
|
|
1343
|
+
"initialData": self.initial_data,
|
|
1344
|
+
})
|
|
1345
|
+
|
|
1346
|
+
@classmethod
|
|
1347
|
+
def from_dict(cls, data: dict) -> CrudExpectationsDefinition:
|
|
1348
|
+
if data is None:
|
|
1349
|
+
return None
|
|
1350
|
+
return cls(
|
|
1351
|
+
base_path=data.get("basePath", ""),
|
|
1352
|
+
id_field=data.get("idField", "id"),
|
|
1353
|
+
id_strategy=data.get("idStrategy", "AUTO_INCREMENT"),
|
|
1354
|
+
initial_data=data.get("initialData"),
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
|
|
1358
|
+
RequestDefinition = HttpRequest
|