aws-durable-execution-sdk-python-testing 1.0.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.
Files changed (52) hide show
  1. aws_durable_execution_sdk_python_testing/__about__.py +4 -0
  2. aws_durable_execution_sdk_python_testing/__init__.py +20 -0
  3. aws_durable_execution_sdk_python_testing/checkpoint/__init__.py +1 -0
  4. aws_durable_execution_sdk_python_testing/checkpoint/processor.py +101 -0
  5. aws_durable_execution_sdk_python_testing/checkpoint/processors/__init__.py +1 -0
  6. aws_durable_execution_sdk_python_testing/checkpoint/processors/base.py +199 -0
  7. aws_durable_execution_sdk_python_testing/checkpoint/processors/callback.py +89 -0
  8. aws_durable_execution_sdk_python_testing/checkpoint/processors/context.py +59 -0
  9. aws_durable_execution_sdk_python_testing/checkpoint/processors/execution.py +52 -0
  10. aws_durable_execution_sdk_python_testing/checkpoint/processors/step.py +124 -0
  11. aws_durable_execution_sdk_python_testing/checkpoint/processors/wait.py +95 -0
  12. aws_durable_execution_sdk_python_testing/checkpoint/transformer.py +104 -0
  13. aws_durable_execution_sdk_python_testing/checkpoint/validators/__init__.py +1 -0
  14. aws_durable_execution_sdk_python_testing/checkpoint/validators/checkpoint.py +242 -0
  15. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/__init__.py +1 -0
  16. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/callback.py +45 -0
  17. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/context.py +73 -0
  18. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/execution.py +47 -0
  19. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/invoke.py +56 -0
  20. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/step.py +106 -0
  21. aws_durable_execution_sdk_python_testing/checkpoint/validators/operations/wait.py +54 -0
  22. aws_durable_execution_sdk_python_testing/checkpoint/validators/transitions.py +66 -0
  23. aws_durable_execution_sdk_python_testing/cli.py +501 -0
  24. aws_durable_execution_sdk_python_testing/client.py +50 -0
  25. aws_durable_execution_sdk_python_testing/exceptions.py +288 -0
  26. aws_durable_execution_sdk_python_testing/execution.py +440 -0
  27. aws_durable_execution_sdk_python_testing/executor.py +1214 -0
  28. aws_durable_execution_sdk_python_testing/invoker.py +327 -0
  29. aws_durable_execution_sdk_python_testing/model.py +3267 -0
  30. aws_durable_execution_sdk_python_testing/observer.py +144 -0
  31. aws_durable_execution_sdk_python_testing/py.typed +1 -0
  32. aws_durable_execution_sdk_python_testing/runner.py +1185 -0
  33. aws_durable_execution_sdk_python_testing/scheduler.py +246 -0
  34. aws_durable_execution_sdk_python_testing/stores/__init__.py +1 -0
  35. aws_durable_execution_sdk_python_testing/stores/base.py +147 -0
  36. aws_durable_execution_sdk_python_testing/stores/filesystem.py +104 -0
  37. aws_durable_execution_sdk_python_testing/stores/memory.py +38 -0
  38. aws_durable_execution_sdk_python_testing/stores/sqlite.py +283 -0
  39. aws_durable_execution_sdk_python_testing/token.py +49 -0
  40. aws_durable_execution_sdk_python_testing/web/__init__.py +1 -0
  41. aws_durable_execution_sdk_python_testing/web/errors.py +8 -0
  42. aws_durable_execution_sdk_python_testing/web/handlers.py +807 -0
  43. aws_durable_execution_sdk_python_testing/web/models.py +266 -0
  44. aws_durable_execution_sdk_python_testing/web/routes.py +684 -0
  45. aws_durable_execution_sdk_python_testing/web/serialization.py +245 -0
  46. aws_durable_execution_sdk_python_testing/web/server.py +243 -0
  47. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/METADATA +221 -0
  48. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/RECORD +52 -0
  49. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/WHEEL +4 -0
  50. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/entry_points.txt +2 -0
  51. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/licenses/LICENSE +175 -0
  52. aws_durable_execution_sdk_python_testing-1.0.0.dist-info/licenses/NOTICE +1 -0
@@ -0,0 +1,684 @@
1
+ """Strongly-typed route parsing system for HTTP request routing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from urllib.parse import unquote
7
+
8
+ from aws_durable_execution_sdk_python_testing.exceptions import (
9
+ UnknownRouteError,
10
+ )
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Route:
15
+ """Base route with segments and pattern matching capabilities."""
16
+
17
+ raw_path: str
18
+ segments: list[str]
19
+
20
+ @classmethod
21
+ def from_route(cls, _route: Route) -> Route:
22
+ """Create a typed route from a base Route.
23
+
24
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
25
+
26
+ Args:
27
+ route: Base route to convert
28
+
29
+ Returns:
30
+ Typed route instance
31
+
32
+ Raises:
33
+ NotImplementedError: This is an abstract method that must be implemented by subclasses
34
+ """
35
+ msg = "Subclasses must implement from_route()"
36
+ raise NotImplementedError(msg)
37
+
38
+ @classmethod
39
+ def from_string(cls, path: str) -> Route:
40
+ """Create a Route from a string.
41
+
42
+ Args:
43
+ path: The raw path string
44
+
45
+ Returns:
46
+ Route instance with parsed segments
47
+ """
48
+ # Remove leading/trailing slashes and split into segments
49
+ segments = [s for s in path.strip("/").split("/") if s]
50
+ return cls(raw_path=path, segments=segments)
51
+
52
+ def matches_pattern(self, pattern: list[str]) -> bool:
53
+ """Check if route matches the given pattern.
54
+
55
+ Args:
56
+ pattern: List of pattern segments. Use '*' for wildcards.
57
+
58
+ Returns:
59
+ True if the route matches the pattern
60
+ """
61
+ if len(self.segments) != len(pattern):
62
+ return False
63
+
64
+ for segment, pattern_part in zip(self.segments, pattern, strict=False):
65
+ if pattern_part not in ("*", segment):
66
+ return False
67
+ return True
68
+
69
+ @classmethod
70
+ def is_match(cls, _route: Route, _method: str) -> bool:
71
+ """Check if the route and HTTP method match this route type.
72
+
73
+ Args:
74
+ _route: Route to check
75
+ _method: HTTP method to check
76
+
77
+ Returns:
78
+ True if the route and method match
79
+
80
+ Raises:
81
+ NotImplementedError: This is an abstract method that must be implemented by subclasses
82
+ """
83
+ msg = "Subclasses must implement is_match()"
84
+ raise NotImplementedError(msg)
85
+
86
+
87
+ @dataclass(frozen=True)
88
+ class StartExecutionRoute(Route):
89
+ """Route: POST /start-durable-execution"""
90
+
91
+ @classmethod
92
+ def is_match(cls, route: Route, method: str) -> bool:
93
+ """Check if the route and HTTP method match this route type.
94
+
95
+ Args:
96
+ route: Route to check
97
+ method: HTTP method to check
98
+
99
+ Returns:
100
+ True if the route and method match
101
+ """
102
+ return route.raw_path == "/start-durable-execution" and method == "POST"
103
+
104
+ @classmethod
105
+ def from_route(cls, route: Route) -> StartExecutionRoute:
106
+ """Create a StartExecutionRoute from a base Route.
107
+
108
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
109
+
110
+ Args:
111
+ route: Base route to convert
112
+
113
+ Returns:
114
+ StartExecutionRoute instance
115
+ """
116
+ return cls(raw_path=route.raw_path, segments=route.segments)
117
+
118
+
119
+ @dataclass(frozen=True)
120
+ class GetDurableExecutionRoute(Route):
121
+ """Route: GET /2025-12-01/durable-executions/{arn}"""
122
+
123
+ arn: str
124
+
125
+ @classmethod
126
+ def is_match(cls, route: Route, method: str) -> bool:
127
+ """Check if the route and HTTP method match this route type.
128
+
129
+ Args:
130
+ route: Route to check
131
+ method: HTTP method to check
132
+
133
+ Returns:
134
+ True if the route and method match
135
+ """
136
+ return (
137
+ route.matches_pattern(["2025-12-01", "durable-executions", "*"])
138
+ and method == "GET"
139
+ )
140
+
141
+ @classmethod
142
+ def from_route(cls, route: Route) -> GetDurableExecutionRoute:
143
+ """Create a GetDurableExecutionRoute from a base Route.
144
+
145
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
146
+
147
+ Args:
148
+ route: Base route to convert
149
+
150
+ Returns:
151
+ GetDurableExecutionRoute instance with extracted ARN
152
+ """
153
+ return cls(
154
+ raw_path=route.raw_path,
155
+ segments=route.segments,
156
+ arn=route.segments[2],
157
+ )
158
+
159
+
160
+ @dataclass(frozen=True)
161
+ class CheckpointDurableExecutionRoute(Route):
162
+ """Route: POST /2025-12-01/durable-executions/{arn}/checkpoint"""
163
+
164
+ arn: str
165
+
166
+ @classmethod
167
+ def is_match(cls, route: Route, method: str) -> bool:
168
+ """Check if the route and HTTP method match this route type.
169
+
170
+ Args:
171
+ route: Route to check
172
+ method: HTTP method to check
173
+
174
+ Returns:
175
+ True if the route and method match
176
+ """
177
+ return (
178
+ route.matches_pattern(
179
+ ["2025-12-01", "durable-executions", "*", "checkpoint"]
180
+ )
181
+ and method == "POST"
182
+ )
183
+
184
+ @classmethod
185
+ def from_route(cls, route: Route) -> CheckpointDurableExecutionRoute:
186
+ """Create a CheckpointDurableExecutionRoute from a base Route.
187
+
188
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
189
+
190
+ Args:
191
+ route: Base route to convert
192
+
193
+ Returns:
194
+ CheckpointDurableExecutionRoute instance with extracted ARN
195
+ """
196
+ return cls(
197
+ raw_path=route.raw_path,
198
+ segments=route.segments,
199
+ arn=route.segments[2],
200
+ )
201
+
202
+
203
+ @dataclass(frozen=True)
204
+ class StopDurableExecutionRoute(Route):
205
+ """Route: POST /2025-12-01/durable-executions/{arn}/stop"""
206
+
207
+ arn: str
208
+
209
+ @classmethod
210
+ def is_match(cls, route: Route, method: str) -> bool:
211
+ """Check if the route and HTTP method match this route type.
212
+
213
+ Args:
214
+ route: Route to check
215
+ method: HTTP method to check
216
+
217
+ Returns:
218
+ True if the route and method match
219
+ """
220
+ return (
221
+ route.matches_pattern(["2025-12-01", "durable-executions", "*", "stop"])
222
+ and method == "POST"
223
+ )
224
+
225
+ @classmethod
226
+ def from_route(cls, route: Route) -> StopDurableExecutionRoute:
227
+ """Create a StopDurableExecutionRoute from a base Route.
228
+
229
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
230
+
231
+ Args:
232
+ route: Base route to convert
233
+
234
+ Returns:
235
+ StopDurableExecutionRoute instance with extracted ARN
236
+ """
237
+ return cls(
238
+ raw_path=route.raw_path,
239
+ segments=route.segments,
240
+ arn=route.segments[2],
241
+ )
242
+
243
+
244
+ @dataclass(frozen=True)
245
+ class GetDurableExecutionStateRoute(Route):
246
+ """Route: GET /2025-12-01/durable-executions/{arn}/state"""
247
+
248
+ arn: str
249
+
250
+ @classmethod
251
+ def is_match(cls, route: Route, method: str) -> bool:
252
+ """Check if the route and HTTP method match this route type.
253
+
254
+ Args:
255
+ route: Route to check
256
+ method: HTTP method to check
257
+
258
+ Returns:
259
+ True if the route and method match
260
+ """
261
+ return (
262
+ route.matches_pattern(["2025-12-01", "durable-executions", "*", "state"])
263
+ and method == "GET"
264
+ )
265
+
266
+ @classmethod
267
+ def from_route(cls, route: Route) -> GetDurableExecutionStateRoute:
268
+ """Create a GetDurableExecutionStateRoute from a base Route.
269
+
270
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
271
+
272
+ Args:
273
+ route: Base route to convert
274
+
275
+ Returns:
276
+ GetDurableExecutionStateRoute instance with extracted ARN
277
+ """
278
+ return cls(
279
+ raw_path=route.raw_path,
280
+ segments=route.segments,
281
+ arn=route.segments[2],
282
+ )
283
+
284
+
285
+ @dataclass(frozen=True)
286
+ class GetDurableExecutionHistoryRoute(Route):
287
+ """Route: GET /2025-12-01/durable-executions/{arn}/history"""
288
+
289
+ arn: str
290
+
291
+ @classmethod
292
+ def is_match(cls, route: Route, method: str) -> bool:
293
+ """Check if the route and HTTP method match this route type.
294
+
295
+ Args:
296
+ route: Route to check
297
+ method: HTTP method to check
298
+
299
+ Returns:
300
+ True if the route and method match
301
+ """
302
+ return (
303
+ route.matches_pattern(["2025-12-01", "durable-executions", "*", "history"])
304
+ and method == "GET"
305
+ )
306
+
307
+ @classmethod
308
+ def from_route(cls, route: Route) -> GetDurableExecutionHistoryRoute:
309
+ """Create a GetDurableExecutionHistoryRoute from a base Route.
310
+
311
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
312
+
313
+ Args:
314
+ route: Base route to convert
315
+
316
+ Returns:
317
+ GetDurableExecutionHistoryRoute instance with extracted ARN
318
+ """
319
+ return cls(
320
+ raw_path=route.raw_path,
321
+ segments=route.segments,
322
+ arn=route.segments[2],
323
+ )
324
+
325
+
326
+ @dataclass(frozen=True)
327
+ class ListDurableExecutionsRoute(Route):
328
+ """Route: GET /2025-12-01/durable-executions"""
329
+
330
+ @classmethod
331
+ def is_match(cls, route: Route, method: str) -> bool:
332
+ """Check if the route and HTTP method match this route type.
333
+
334
+ Args:
335
+ route: Route to check
336
+ method: HTTP method to check
337
+
338
+ Returns:
339
+ True if the route and method match
340
+ """
341
+ return (
342
+ route.matches_pattern(["2025-12-01", "durable-executions"])
343
+ and method == "GET"
344
+ )
345
+
346
+ @classmethod
347
+ def from_route(cls, route: Route) -> ListDurableExecutionsRoute:
348
+ """Create a ListDurableExecutionsRoute from a base Route.
349
+
350
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
351
+
352
+ Args:
353
+ route: Base route to convert
354
+
355
+ Returns:
356
+ ListDurableExecutionsRoute instance
357
+ """
358
+ return cls(raw_path=route.raw_path, segments=route.segments)
359
+
360
+
361
+ @dataclass(frozen=True)
362
+ class ListDurableExecutionsByFunctionRoute(Route):
363
+ """Route: GET /2025-12-01/functions/{function_name}/durable-executions"""
364
+
365
+ function_name: str
366
+
367
+ @classmethod
368
+ def is_match(cls, route: Route, method: str) -> bool:
369
+ """Check if the route and HTTP method match this route type.
370
+
371
+ Args:
372
+ route: Route to check
373
+ method: HTTP method to check
374
+
375
+ Returns:
376
+ True if the route and method match
377
+ """
378
+ return (
379
+ route.matches_pattern(
380
+ ["2025-12-01", "functions", "*", "durable-executions"]
381
+ )
382
+ and method == "GET"
383
+ )
384
+
385
+ @classmethod
386
+ def from_route(cls, route: Route) -> ListDurableExecutionsByFunctionRoute:
387
+ """Create a ListDurableExecutionsByFunctionRoute from a base Route.
388
+
389
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
390
+
391
+ Args:
392
+ route: Base route to convert
393
+
394
+ Returns:
395
+ ListDurableExecutionsByFunctionRoute instance with extracted function name
396
+ """
397
+ return cls(
398
+ raw_path=route.raw_path,
399
+ segments=route.segments,
400
+ function_name=route.segments[2],
401
+ )
402
+
403
+
404
+ @dataclass(frozen=True)
405
+ class BytesPayloadRoute(Route):
406
+ """Base class for routes that handle raw bytes payloads instead of JSON."""
407
+
408
+
409
+ @dataclass(frozen=True)
410
+ class CallbackSuccessRoute(BytesPayloadRoute):
411
+ """Route: POST /2025-12-01/durable-execution-callbacks/{callback_id}/succeed"""
412
+
413
+ callback_id: str
414
+
415
+ @classmethod
416
+ def is_match(cls, route: Route, method: str) -> bool:
417
+ """Check if the route and HTTP method match this route type.
418
+
419
+ Args:
420
+ route: Route to check
421
+ method: HTTP method to check
422
+
423
+ Returns:
424
+ True if the route and method match
425
+ """
426
+ return (
427
+ route.matches_pattern(
428
+ ["2025-12-01", "durable-execution-callbacks", "*", "succeed"]
429
+ )
430
+ and method == "POST"
431
+ )
432
+
433
+ @classmethod
434
+ def from_route(cls, route: Route) -> CallbackSuccessRoute:
435
+ """Create a CallbackSuccessRoute from a base Route.
436
+
437
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
438
+
439
+ Args:
440
+ route: Base route to convert
441
+
442
+ Returns:
443
+ CallbackSuccessRoute instance with extracted callback ID
444
+ """
445
+ return cls(
446
+ raw_path=route.raw_path,
447
+ segments=route.segments,
448
+ callback_id=unquote(route.segments[2]),
449
+ )
450
+
451
+
452
+ @dataclass(frozen=True)
453
+ class CallbackFailureRoute(BytesPayloadRoute):
454
+ """Route: POST /2025-12-01/durable-execution-callbacks/{callback_id}/fail"""
455
+
456
+ callback_id: str
457
+
458
+ @classmethod
459
+ def is_match(cls, route: Route, method: str) -> bool:
460
+ """Check if the route and HTTP method match this route type.
461
+
462
+ Args:
463
+ route: Route to check
464
+ method: HTTP method to check
465
+
466
+ Returns:
467
+ True if the route and method match
468
+ """
469
+ return (
470
+ route.matches_pattern(
471
+ ["2025-12-01", "durable-execution-callbacks", "*", "fail"]
472
+ )
473
+ and method == "POST"
474
+ )
475
+
476
+ @classmethod
477
+ def from_route(cls, route: Route) -> CallbackFailureRoute:
478
+ """Create a CallbackFailureRoute from a base Route.
479
+
480
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
481
+
482
+ Args:
483
+ route: Base route to convert
484
+
485
+ Returns:
486
+ CallbackFailureRoute instance with extracted callback ID
487
+ """
488
+ return cls(
489
+ raw_path=route.raw_path,
490
+ segments=route.segments,
491
+ callback_id=unquote(route.segments[2]),
492
+ )
493
+
494
+
495
+ @dataclass(frozen=True)
496
+ class CallbackHeartbeatRoute(Route):
497
+ """Route: POST /2025-12-01/durable-execution-callbacks/{callback_id}/heartbeat"""
498
+
499
+ callback_id: str
500
+
501
+ @classmethod
502
+ def is_match(cls, route: Route, method: str) -> bool:
503
+ """Check if the route and HTTP method match this route type.
504
+
505
+ Args:
506
+ route: Route to check
507
+ method: HTTP method to check
508
+
509
+ Returns:
510
+ True if the route and method match
511
+ """
512
+ return (
513
+ route.matches_pattern(
514
+ ["2025-12-01", "durable-execution-callbacks", "*", "heartbeat"]
515
+ )
516
+ and method == "POST"
517
+ )
518
+
519
+ @classmethod
520
+ def from_route(cls, route: Route) -> CallbackHeartbeatRoute:
521
+ """Create a CallbackHeartbeatRoute from a base Route.
522
+
523
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
524
+
525
+ Args:
526
+ route: Base route to convert
527
+
528
+ Returns:
529
+ CallbackHeartbeatRoute instance with extracted callback ID
530
+ """
531
+ return cls(
532
+ raw_path=route.raw_path,
533
+ segments=route.segments,
534
+ callback_id=unquote(route.segments[2]),
535
+ )
536
+
537
+
538
+ @dataclass(frozen=True)
539
+ class HealthRoute(Route):
540
+ """Route: GET /health"""
541
+
542
+ @classmethod
543
+ def is_match(cls, route: Route, method: str) -> bool:
544
+ """Check if the route and HTTP method match this route type.
545
+
546
+ Args:
547
+ route: Route to check
548
+ method: HTTP method to check
549
+
550
+ Returns:
551
+ True if the route and method match
552
+ """
553
+ return route.raw_path == "/health" and method == "GET"
554
+
555
+ @classmethod
556
+ def from_route(cls, route: Route) -> HealthRoute:
557
+ """Create a HealthRoute from a base Route.
558
+
559
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
560
+
561
+ Args:
562
+ route: Base route to convert
563
+
564
+ Returns:
565
+ HealthRoute instance
566
+ """
567
+ return cls(raw_path=route.raw_path, segments=route.segments)
568
+
569
+
570
+ @dataclass(frozen=True)
571
+ class UpdateLambdaEndpointRoute(Route):
572
+ """Route: PUT /lambda-endpoint"""
573
+
574
+ @classmethod
575
+ def is_match(cls, route: Route, method: str) -> bool:
576
+ """Check if the route and HTTP method match this route type.
577
+
578
+ Args:
579
+ route: Route to check
580
+ method: HTTP method to check
581
+
582
+ Returns:
583
+ True if the route and method match
584
+ """
585
+ return route.raw_path == "/lambda-endpoint" and method == "PUT"
586
+
587
+ @classmethod
588
+ def from_route(cls, route: Route) -> UpdateLambdaEndpointRoute:
589
+ """Create UpdateLambdaEndpointRoute from base route.
590
+
591
+ Args:
592
+ route: Base route to convert
593
+
594
+ Returns:
595
+ UpdateLambdaEndpointRoute instance
596
+ """
597
+ return cls(raw_path=route.raw_path, segments=route.segments)
598
+
599
+
600
+ @dataclass(frozen=True)
601
+ class MetricsRoute(Route):
602
+ """Route: GET /metrics"""
603
+
604
+ @classmethod
605
+ def is_match(cls, route: Route, method: str) -> bool:
606
+ """Check if the route and HTTP method match this route type.
607
+
608
+ Args:
609
+ route: Route to check
610
+ method: HTTP method to check
611
+
612
+ Returns:
613
+ True if the route and method match
614
+ """
615
+ return route.raw_path == "/metrics" and method == "GET"
616
+
617
+ @classmethod
618
+ def from_route(cls, route: Route) -> MetricsRoute:
619
+ """Create a MetricsRoute from a base Route.
620
+
621
+ Note: Call is_match(route, method) first to ensure the route is valid for this type.
622
+
623
+ Args:
624
+ route: Base route to convert
625
+
626
+ Returns:
627
+ MetricsRoute instance
628
+ """
629
+ return cls(raw_path=route.raw_path, segments=route.segments)
630
+
631
+
632
+ # Default registry of all route types for matching
633
+ DEFAULT_ROUTE_TYPES: list[type[Route]] = [
634
+ StartExecutionRoute,
635
+ GetDurableExecutionRoute,
636
+ CheckpointDurableExecutionRoute,
637
+ StopDurableExecutionRoute,
638
+ GetDurableExecutionStateRoute,
639
+ GetDurableExecutionHistoryRoute,
640
+ ListDurableExecutionsRoute,
641
+ ListDurableExecutionsByFunctionRoute,
642
+ CallbackSuccessRoute,
643
+ CallbackFailureRoute,
644
+ CallbackHeartbeatRoute,
645
+ HealthRoute,
646
+ UpdateLambdaEndpointRoute,
647
+ MetricsRoute,
648
+ ]
649
+
650
+
651
+ class Router:
652
+ """HTTP request router that matches routes to strongly-typed route objects."""
653
+
654
+ def __init__(self, route_types: list[type[Route]] | None = None) -> None:
655
+ """Initialize the router with route types.
656
+
657
+ Args:
658
+ route_types: List of route type classes to use for matching.
659
+ If None, uses the default route types.
660
+ """
661
+ self._route_types = (
662
+ route_types if route_types is not None else DEFAULT_ROUTE_TYPES
663
+ )
664
+
665
+ def find_route(self, path: str, method: str) -> Route:
666
+ """Find a matching route for the given path and HTTP method.
667
+
668
+ Args:
669
+ path: The raw path string to parse
670
+ method: The HTTP method (GET, POST, etc.)
671
+
672
+ Returns:
673
+ Strongly-typed Route instance
674
+
675
+ Raises:
676
+ UnknownRouteError: If the path and method don't match any known pattern
677
+ """
678
+ base_route = Route.from_string(path)
679
+
680
+ for route_type in self._route_types:
681
+ if route_type.is_match(base_route, method):
682
+ return route_type.from_route(base_route)
683
+
684
+ raise UnknownRouteError(method, path)