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/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