netrun-sim 0.1.2__cp38-abi3-macosx_10_12_x86_64.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.
netrun_sim/__init__.py ADDED
@@ -0,0 +1,56 @@
1
+ # Re-export all types from the native extension
2
+ from netrun_sim.netrun_sim import *
3
+
4
+ __all__ = [
5
+ # Exceptions
6
+ "NetrunError",
7
+ "PacketNotFoundError",
8
+ "EpochNotFoundError",
9
+ "EpochNotRunningError",
10
+ "EpochNotStartableError",
11
+ "CannotFinishNonEmptyEpochError",
12
+ "UnsentOutputSalvoError",
13
+ "PacketNotInNodeError",
14
+ "OutputPortNotFoundError",
15
+ "OutputPortFullError",
16
+ "SalvoConditionNotFoundError",
17
+ "SalvoConditionNotMetError",
18
+ "MaxSalvosExceededError",
19
+ "NodeNotFoundError",
20
+ "PacketNotAtInputPortError",
21
+ "InputPortNotFoundError",
22
+ "InputPortFullError",
23
+ "CannotMovePacketFromRunningEpochError",
24
+ "CannotMovePacketIntoRunningEpochError",
25
+ "EdgeNotFoundError",
26
+ "UnconnectedOutputPortError",
27
+ "GraphValidationError",
28
+ # Graph types
29
+ "PacketCount",
30
+ "PacketCountN",
31
+ "MaxSalvos",
32
+ "MaxSalvosFinite",
33
+ "PortSlotSpec",
34
+ "PortSlotSpecFinite",
35
+ "PortState",
36
+ "PortStateNumeric",
37
+ "SalvoConditionTerm",
38
+ "Port",
39
+ "PortType",
40
+ "PortRef",
41
+ "Edge",
42
+ "EdgeRef",
43
+ "SalvoCondition",
44
+ "Node",
45
+ "Graph",
46
+ # NetSim types
47
+ "PacketLocation",
48
+ "EpochState",
49
+ "Packet",
50
+ "Salvo",
51
+ "Epoch",
52
+ "NetAction",
53
+ "NetEvent",
54
+ "NetActionResponseData",
55
+ "NetSim",
56
+ ]
@@ -0,0 +1,901 @@
1
+ """Type stubs for netrun_sim - Flow-based development runtime simulation."""
2
+
3
+ from typing import Dict, List, Optional, Tuple, Union
4
+ from ulid import ULID
5
+
6
+ # === Exceptions ===
7
+
8
+ class NetrunError(Exception):
9
+ """Base exception for all netrun errors."""
10
+
11
+ ...
12
+
13
+ class PacketNotFoundError(NetrunError):
14
+ """Packet with the given ID was not found."""
15
+
16
+ ...
17
+
18
+ class EpochNotFoundError(NetrunError):
19
+ """Epoch with the given ID was not found."""
20
+
21
+ ...
22
+
23
+ class EpochNotRunningError(NetrunError):
24
+ """Epoch exists but is not in Running state."""
25
+
26
+ ...
27
+
28
+ class EpochNotStartableError(NetrunError):
29
+ """Epoch exists but is not in Startable state."""
30
+
31
+ ...
32
+
33
+ class CannotFinishNonEmptyEpochError(NetrunError):
34
+ """Cannot finish epoch because it still contains packets."""
35
+
36
+ ...
37
+
38
+ class UnsentOutputSalvoError(NetrunError):
39
+ """Cannot finish epoch because output port still has unsent packets."""
40
+
41
+ ...
42
+
43
+ class PacketNotInNodeError(NetrunError):
44
+ """Packet is not inside the specified epoch's node location."""
45
+
46
+ ...
47
+
48
+ class OutputPortNotFoundError(NetrunError):
49
+ """Output port does not exist on the node."""
50
+
51
+ ...
52
+
53
+ class OutputPortFullError(NetrunError):
54
+ """Output port has reached its capacity."""
55
+
56
+ ...
57
+
58
+ class SalvoConditionNotFoundError(NetrunError):
59
+ """Salvo condition with the given name was not found."""
60
+
61
+ ...
62
+
63
+ class SalvoConditionNotMetError(NetrunError):
64
+ """Salvo condition exists but its term is not satisfied."""
65
+
66
+ ...
67
+
68
+ class MaxSalvosExceededError(NetrunError):
69
+ """Maximum number of salvos reached for this condition."""
70
+
71
+ ...
72
+
73
+ class NodeNotFoundError(NetrunError):
74
+ """Node with the given name was not found."""
75
+
76
+ ...
77
+
78
+ class PacketNotAtInputPortError(NetrunError):
79
+ """Packet is not at the expected input port."""
80
+
81
+ ...
82
+
83
+ class InputPortNotFoundError(NetrunError):
84
+ """Input port does not exist on the node."""
85
+
86
+ ...
87
+
88
+ class InputPortFullError(NetrunError):
89
+ """Input port has reached its capacity."""
90
+
91
+ ...
92
+
93
+ class CannotMovePacketFromRunningEpochError(NetrunError):
94
+ """Cannot move packet out of a running epoch."""
95
+
96
+ ...
97
+
98
+ class CannotMovePacketIntoRunningEpochError(NetrunError):
99
+ """Cannot move packet into a running epoch."""
100
+
101
+ ...
102
+
103
+ class EdgeNotFoundError(NetrunError):
104
+ """Edge does not exist in the graph."""
105
+
106
+ ...
107
+
108
+ class UnconnectedOutputPortError(NetrunError):
109
+ """Output port is not connected to any edge."""
110
+
111
+ ...
112
+
113
+ class GraphValidationError(NetrunError):
114
+ """Graph validation failed."""
115
+
116
+ ...
117
+
118
+ # === Graph Types ===
119
+
120
+ class PacketCount:
121
+ """Specifies how many packets to take from a port in a salvo."""
122
+
123
+ All: PacketCount
124
+
125
+ @staticmethod
126
+ def all() -> PacketCount: ...
127
+ @staticmethod
128
+ def count(n: int) -> PacketCountN: ...
129
+
130
+ class PacketCountN:
131
+ """PacketCount with a specific count limit."""
132
+ @property
133
+ def count(self) -> int: ...
134
+
135
+ class MaxSalvos:
136
+ """Specifies the maximum number of times a salvo condition can trigger."""
137
+
138
+ Infinite: MaxSalvos
139
+
140
+ @staticmethod
141
+ def infinite() -> MaxSalvos: ...
142
+ @staticmethod
143
+ def finite(n: int) -> MaxSalvosFinite: ...
144
+
145
+ class MaxSalvosFinite:
146
+ """Finite max salvos with a specific limit."""
147
+ @property
148
+ def max(self) -> int: ...
149
+
150
+ class PortSlotSpec:
151
+ """Port capacity specification."""
152
+
153
+ Infinite: PortSlotSpec
154
+ Finite: PortSlotSpec
155
+
156
+ @staticmethod
157
+ def infinite() -> PortSlotSpec: ...
158
+ @staticmethod
159
+ def finite(n: int) -> PortSlotSpecFinite: ...
160
+
161
+ class PortSlotSpecFinite:
162
+ """Finite port capacity with a specific limit."""
163
+ @property
164
+ def capacity(self) -> int: ...
165
+
166
+ class PortState:
167
+ """Port state predicate for salvo conditions."""
168
+
169
+ Empty: PortState
170
+ Full: PortState
171
+ NonEmpty: PortState
172
+ NonFull: PortState
173
+
174
+ @staticmethod
175
+ def empty() -> PortState: ...
176
+ @staticmethod
177
+ def full() -> PortState: ...
178
+ @staticmethod
179
+ def non_empty() -> PortState: ...
180
+ @staticmethod
181
+ def non_full() -> PortState: ...
182
+ @staticmethod
183
+ def equals(n: int) -> PortStateNumeric: ...
184
+ @staticmethod
185
+ def less_than(n: int) -> PortStateNumeric: ...
186
+ @staticmethod
187
+ def greater_than(n: int) -> PortStateNumeric: ...
188
+ @staticmethod
189
+ def equals_or_less_than(n: int) -> PortStateNumeric: ...
190
+ @staticmethod
191
+ def equals_or_greater_than(n: int) -> PortStateNumeric: ...
192
+
193
+ class PortStateNumeric:
194
+ """Numeric port state predicate."""
195
+ @property
196
+ def kind(self) -> str: ...
197
+ @property
198
+ def value(self) -> int: ...
199
+
200
+ class SalvoConditionTerm:
201
+ """Boolean expression over port states.
202
+
203
+ Use the static methods to construct terms:
204
+ - `true_()` / `false_()`: Constant boolean values
205
+ - `port(name, state)`: Check if a port matches a state predicate
206
+ - `and_(terms)` / `or_(terms)`: Logical combinations
207
+ - `not_(term)`: Logical negation
208
+
209
+ Example:
210
+ # Trigger when both input ports have packets
211
+ term = SalvoConditionTerm.and_([
212
+ SalvoConditionTerm.port("in1", PortState.non_empty()),
213
+ SalvoConditionTerm.port("in2", PortState.non_empty()),
214
+ ])
215
+
216
+ # Source node with no input ports
217
+ term = SalvoConditionTerm.true_()
218
+ """
219
+
220
+ @staticmethod
221
+ def true_() -> SalvoConditionTerm:
222
+ """Create a term that is always true. Useful for source nodes with no input ports."""
223
+ ...
224
+
225
+ @staticmethod
226
+ def false_() -> SalvoConditionTerm:
227
+ """Create a term that is always false. Useful as a placeholder or with not_()."""
228
+ ...
229
+
230
+ @staticmethod
231
+ def port(
232
+ port_name: str, state: Union[PortState, PortStateNumeric]
233
+ ) -> SalvoConditionTerm:
234
+ """Create a term that checks if a port matches a state predicate."""
235
+ ...
236
+
237
+ @staticmethod
238
+ def and_(terms: List[SalvoConditionTerm]) -> SalvoConditionTerm:
239
+ """Create a term that is true when all sub-terms are true."""
240
+ ...
241
+
242
+ @staticmethod
243
+ def or_(terms: List[SalvoConditionTerm]) -> SalvoConditionTerm:
244
+ """Create a term that is true when at least one sub-term is true."""
245
+ ...
246
+
247
+ @staticmethod
248
+ def not_(term: SalvoConditionTerm) -> SalvoConditionTerm:
249
+ """Create a term that is true when the sub-term is false."""
250
+ ...
251
+
252
+ @property
253
+ def kind(self) -> str:
254
+ """Get the term kind: 'True', 'False', 'Port', 'And', 'Or', or 'Not'."""
255
+ ...
256
+
257
+ def get_port_name(self) -> Optional[str]:
258
+ """Get the port name (for Port terms only, None otherwise)."""
259
+ ...
260
+
261
+ def get_port_state(self) -> Optional[Union[PortState, PortStateNumeric]]:
262
+ """Get the port state (for Port terms only, None otherwise)."""
263
+ ...
264
+
265
+ def get_terms(self) -> Optional[List[SalvoConditionTerm]]:
266
+ """Get the sub-terms (for And/Or terms only, None otherwise)."""
267
+ ...
268
+
269
+ def get_inner(self) -> Optional[SalvoConditionTerm]:
270
+ """Get the inner term (for Not terms only, None otherwise)."""
271
+ ...
272
+
273
+ class Port:
274
+ """A port on a node."""
275
+
276
+ def __init__(
277
+ self, slots_spec: Optional[Union[PortSlotSpec, PortSlotSpecFinite]] = None
278
+ ) -> None: ...
279
+ @property
280
+ def slots_spec(self) -> Union[PortSlotSpec, PortSlotSpecFinite]: ...
281
+
282
+ class PortType:
283
+ """Port type: Input or Output."""
284
+
285
+ Input: PortType
286
+ Output: PortType
287
+
288
+ class PortRef:
289
+ """Reference to a specific port on a node."""
290
+
291
+ def __init__(self, node_name: str, port_type: PortType, port_name: str) -> None: ...
292
+ @property
293
+ def node_name(self) -> str: ...
294
+ @property
295
+ def port_type(self) -> PortType: ...
296
+ @property
297
+ def port_name(self) -> str: ...
298
+ def __eq__(self, other: object) -> bool: ...
299
+ def __hash__(self) -> int: ...
300
+
301
+ class Edge:
302
+ """A connection between two ports in the graph."""
303
+
304
+ def __init__(self, source: PortRef, target: PortRef) -> None: ...
305
+ @property
306
+ def source(self) -> PortRef: ...
307
+ @property
308
+ def target(self) -> PortRef: ...
309
+ def __eq__(self, other: object) -> bool: ...
310
+ def __hash__(self) -> int: ...
311
+
312
+ # Type alias for ports parameter
313
+ SalvoConditionPorts = Union[str, List[str], Dict[str, Union[PacketCount, PacketCountN]]]
314
+
315
+ class SalvoCondition:
316
+ """A condition that defines when packets can trigger an epoch or be sent.
317
+
318
+ Args:
319
+ max_salvos: Maximum number of times this condition can trigger.
320
+ Use MaxSalvos.finite(n) for a finite limit or MaxSalvos.Infinite for no limit.
321
+ ports: Which ports' packets are included. Can be:
322
+ - A single port name (str) - defaults to PacketCount.All
323
+ - A list of port names - each defaults to PacketCount.All
324
+ - A dict mapping port names to PacketCount values
325
+ term: The boolean expression that must be satisfied.
326
+ """
327
+
328
+ def __init__(
329
+ self,
330
+ max_salvos: Union[MaxSalvos, MaxSalvosFinite],
331
+ ports: SalvoConditionPorts,
332
+ term: SalvoConditionTerm,
333
+ ) -> None: ...
334
+ @property
335
+ def max_salvos(self) -> Union[MaxSalvos, MaxSalvosFinite]: ...
336
+ @property
337
+ def ports(self) -> Dict[str, Union[PacketCount, PacketCountN]]: ...
338
+ @property
339
+ def term(self) -> SalvoConditionTerm: ...
340
+
341
+ class Node:
342
+ """A processing node in the graph."""
343
+
344
+ def __init__(
345
+ self,
346
+ name: str,
347
+ in_ports: Optional[Dict[str, Port]] = None,
348
+ out_ports: Optional[Dict[str, Port]] = None,
349
+ in_salvo_conditions: Optional[Dict[str, SalvoCondition]] = None,
350
+ out_salvo_conditions: Optional[Dict[str, SalvoCondition]] = None,
351
+ ) -> None: ...
352
+ @property
353
+ def name(self) -> str: ...
354
+ @property
355
+ def in_ports(self) -> Dict[str, Port]: ...
356
+ @property
357
+ def out_ports(self) -> Dict[str, Port]: ...
358
+ @property
359
+ def in_salvo_conditions(self) -> Dict[str, SalvoCondition]: ...
360
+ @property
361
+ def out_salvo_conditions(self) -> Dict[str, SalvoCondition]: ...
362
+
363
+ class Graph:
364
+ """The static topology of a flow-based network."""
365
+
366
+ def __init__(self, nodes: List[Node], edges: List[Edge]) -> None: ...
367
+ def nodes(self) -> Dict[str, Node]: ...
368
+ def edges(self) -> List[Edge]: ...
369
+ def validate(self) -> List[GraphValidationError]: ...
370
+
371
+ # === NetSim Types ===
372
+
373
+ class PacketLocation:
374
+ """Where a packet is located in the network.
375
+
376
+ Packets can be in one of five locations:
377
+ - `node(epoch_id)`: Inside a running/startable epoch
378
+ - `input_port(node_name, port_name)`: Waiting at a node's input port
379
+ - `output_port(epoch_id, port_name)`: Loaded into an epoch's output port
380
+ - `edge(edge)`: In transit between nodes
381
+ - `outside_net()`: External to the network
382
+
383
+ Example:
384
+ loc = PacketLocation.input_port("NodeB", "in")
385
+ assert loc.kind == "InputPort"
386
+ assert loc.node_name == "NodeB"
387
+ assert loc.port_name == "in"
388
+ """
389
+
390
+ @staticmethod
391
+ def node(epoch_id: Union[ULID, str]) -> PacketLocation:
392
+ """Create a location inside an epoch."""
393
+ ...
394
+
395
+ @staticmethod
396
+ def input_port(node_name: str, port_name: str) -> PacketLocation:
397
+ """Create a location at a node's input port."""
398
+ ...
399
+
400
+ @staticmethod
401
+ def output_port(epoch_id: Union[ULID, str], port_name: str) -> PacketLocation:
402
+ """Create a location at an epoch's output port."""
403
+ ...
404
+
405
+ @staticmethod
406
+ def edge(edge: Edge) -> PacketLocation:
407
+ """Create a location on an edge (in transit)."""
408
+ ...
409
+
410
+ @staticmethod
411
+ def outside_net() -> PacketLocation:
412
+ """Create a location outside the network."""
413
+ ...
414
+
415
+ @property
416
+ def kind(self) -> str:
417
+ """Get the location kind: 'Node', 'InputPort', 'OutputPort', 'Edge', or 'OutsideNet'."""
418
+ ...
419
+
420
+ @property
421
+ def node_name(self) -> Optional[str]:
422
+ """Get the node name (for InputPort locations only, None otherwise)."""
423
+ ...
424
+
425
+ @property
426
+ def port_name(self) -> Optional[str]:
427
+ """Get the port name (for InputPort and OutputPort locations only, None otherwise)."""
428
+ ...
429
+
430
+ @property
431
+ def epoch_id(self) -> Optional[str]:
432
+ """Get the epoch ID (for Node and OutputPort locations only, None otherwise)."""
433
+ ...
434
+
435
+ def get_edge(self) -> Optional[Edge]:
436
+ """Get the edge (for Edge locations only, None otherwise)."""
437
+ ...
438
+
439
+ class EpochState:
440
+ """Epoch lifecycle state."""
441
+
442
+ Startable: EpochState
443
+ Running: EpochState
444
+ Finished: EpochState
445
+
446
+ class Packet:
447
+ """A packet in the network."""
448
+
449
+ @property
450
+ def id(self) -> str: ...
451
+ @property
452
+ def location(self) -> PacketLocation: ...
453
+ def get_id(self) -> ULID: ...
454
+
455
+ class Salvo:
456
+ """A collection of packets entering or exiting a node."""
457
+
458
+ def __init__(
459
+ self, salvo_condition: str, packets: List[Tuple[str, str]]
460
+ ) -> None: ...
461
+ @property
462
+ def salvo_condition(self) -> str: ...
463
+ @property
464
+ def packets(self) -> List[Tuple[str, str]]: ...
465
+
466
+ class OrphanedPacketInfo:
467
+ """Information about a packet sent to an unconnected output port.
468
+
469
+ When a packet is sent from an output port that has no connected edge,
470
+ it is moved to OutsideNet and tracked as "orphaned".
471
+ """
472
+
473
+ @property
474
+ def packet_id(self) -> str:
475
+ """The ID of the orphaned packet."""
476
+ ...
477
+
478
+ @property
479
+ def from_port(self) -> str:
480
+ """The name of the output port the packet was sent from."""
481
+ ...
482
+
483
+ @property
484
+ def salvo_condition(self) -> str:
485
+ """The salvo condition that triggered the send."""
486
+ ...
487
+
488
+ class Epoch:
489
+ """An execution instance of a node."""
490
+
491
+ @property
492
+ def id(self) -> str: ...
493
+ @property
494
+ def node_name(self) -> str: ...
495
+ @property
496
+ def in_salvo(self) -> Salvo: ...
497
+ @property
498
+ def out_salvos(self) -> List[Salvo]: ...
499
+ @property
500
+ def state(self) -> EpochState: ...
501
+ @property
502
+ def orphaned_packets(self) -> List[OrphanedPacketInfo]:
503
+ """Packets that were sent to unconnected output ports (moved to OutsideNet)."""
504
+ ...
505
+ def get_id(self) -> ULID: ...
506
+ def start_time(self) -> int: ...
507
+
508
+ class NetAction:
509
+ """An action to perform on the network.
510
+
511
+ Actions are executed via `NetSim.do_action()` and return a tuple of
512
+ (response_data, events).
513
+
514
+ Example:
515
+ # Create and start an epoch manually
516
+ salvo = Salvo(salvo_condition="trigger", packets=[("in", packet_id)])
517
+ response, events = net.do_action(NetAction.create_epoch("NodeB", salvo))
518
+ epoch = response.epoch # CreatedEpoch response
519
+
520
+ response, events = net.do_action(NetAction.start_epoch(epoch.id))
521
+ epoch = response.epoch # StartedEpoch response
522
+ """
523
+
524
+ @staticmethod
525
+ def run_step() -> NetAction:
526
+ """Run one step of automatic packet flow.
527
+
528
+ Moves packets from edges to input ports, then checks input salvo
529
+ conditions to create new epochs. Repeats until no more progress can be made.
530
+ """
531
+ ...
532
+
533
+ @staticmethod
534
+ def create_packet(epoch_id: Optional[Union[ULID, str]] = None) -> NetAction:
535
+ """Create a new packet.
536
+
537
+ Args:
538
+ epoch_id: If provided, create packet inside this epoch.
539
+ If None, create packet outside the network.
540
+
541
+ Returns NetActionResponseData.Packet with the new packet ID.
542
+ """
543
+ ...
544
+
545
+ @staticmethod
546
+ def consume_packet(packet_id: Union[ULID, str]) -> NetAction:
547
+ """Consume a packet (normal removal from the network)."""
548
+ ...
549
+
550
+ @staticmethod
551
+ def destroy_packet(packet_id: Union[ULID, str]) -> NetAction:
552
+ """Destroy a packet (abnormal removal, e.g., due to error)."""
553
+ ...
554
+
555
+ @staticmethod
556
+ def start_epoch(epoch_id: Union[ULID, str]) -> NetAction:
557
+ """Start a Startable epoch, transitioning it to Running state.
558
+
559
+ Returns NetActionResponseData.StartedEpoch with the epoch.
560
+ """
561
+ ...
562
+
563
+ @staticmethod
564
+ def finish_epoch(epoch_id: Union[ULID, str]) -> NetAction:
565
+ """Finish a Running epoch.
566
+
567
+ The epoch must be empty (no packets inside) and all output salvos
568
+ must have been sent.
569
+
570
+ Returns NetActionResponseData.FinishedEpoch with the epoch.
571
+ """
572
+ ...
573
+
574
+ @staticmethod
575
+ def cancel_epoch(epoch_id: Union[ULID, str]) -> NetAction:
576
+ """Cancel an epoch and destroy all packets inside it.
577
+
578
+ Returns NetActionResponseData.CancelledEpoch with the epoch and
579
+ list of destroyed packet IDs.
580
+ """
581
+ ...
582
+
583
+ @staticmethod
584
+ def create_epoch(node_name: str, salvo: Salvo) -> NetAction:
585
+ """Manually create an epoch with specified packets.
586
+
587
+ Bypasses the normal salvo condition triggering mechanism.
588
+ The epoch is created in Startable state - call start_epoch() to
589
+ begin execution.
590
+
591
+ Args:
592
+ node_name: The node to create the epoch on.
593
+ salvo: The input salvo (packets must be at the node's input ports).
594
+ For source nodes with no inputs, use an empty packets list.
595
+
596
+ Returns NetActionResponseData.CreatedEpoch with the epoch.
597
+ """
598
+ ...
599
+
600
+ @staticmethod
601
+ def load_packet_into_output_port(
602
+ packet_id: Union[ULID, str], port_name: str
603
+ ) -> NetAction:
604
+ """Move a packet from inside an epoch to one of its output ports."""
605
+ ...
606
+
607
+ @staticmethod
608
+ def send_output_salvo(
609
+ epoch_id: Union[ULID, str], salvo_condition_name: str
610
+ ) -> NetAction:
611
+ """Send packets from output ports onto edges.
612
+
613
+ The salvo condition must be satisfied for this to succeed.
614
+ """
615
+ ...
616
+
617
+ @staticmethod
618
+ def transport_packet_to_location(
619
+ packet_id: Union[ULID, str], destination: PacketLocation
620
+ ) -> NetAction:
621
+ """Transport a packet to a new location.
622
+
623
+ Cannot move packets into or out of Running epochs.
624
+ """
625
+ ...
626
+
627
+ class NetEvent:
628
+ """An event that occurred during a network action.
629
+
630
+ Event kinds:
631
+ - `PacketCreated`: A packet was created
632
+ - `PacketConsumed`: A packet was consumed (removed from the network)
633
+ - `PacketDestroyed`: A packet was destroyed (e.g., when epoch cancelled)
634
+ - `EpochCreated`: An epoch was created
635
+ - `EpochStarted`: An epoch transitioned to Running state
636
+ - `EpochFinished`: An epoch completed successfully
637
+ - `EpochCancelled`: An epoch was cancelled
638
+ - `PacketMoved`: A packet moved from one location to another
639
+ - `InputSalvoTriggered`: An input salvo condition was triggered
640
+ - `OutputSalvoTriggered`: An output salvo condition was triggered
641
+ - `PacketOrphaned`: A packet was sent to an unconnected output port
642
+ """
643
+
644
+ @property
645
+ def kind(self) -> str:
646
+ """Get the event kind (e.g., 'PacketMoved', 'EpochCreated', 'PacketOrphaned')."""
647
+ ...
648
+
649
+ @property
650
+ def timestamp(self) -> int:
651
+ """Get the timestamp (UTC microseconds) when the event occurred."""
652
+ ...
653
+
654
+ @property
655
+ def packet_id(self) -> Optional[str]:
656
+ """Get the packet ID for packet-related events."""
657
+ ...
658
+
659
+ @property
660
+ def epoch_id(self) -> Optional[str]:
661
+ """Get the epoch ID for epoch-related events."""
662
+ ...
663
+
664
+ @property
665
+ def from_location(self) -> Optional[PacketLocation]:
666
+ """Get the source location for PacketMoved events."""
667
+ ...
668
+
669
+ @property
670
+ def to_location(self) -> Optional[PacketLocation]:
671
+ """Get the destination location for PacketMoved events."""
672
+ ...
673
+
674
+ @property
675
+ def salvo_condition(self) -> Optional[str]:
676
+ """Get the salvo condition name for salvo-triggered and PacketOrphaned events."""
677
+ ...
678
+
679
+ @property
680
+ def node_name(self) -> Optional[str]:
681
+ """Get the node name for PacketOrphaned events."""
682
+ ...
683
+
684
+ @property
685
+ def orphaned_port_name(self) -> Optional[str]:
686
+ """Get the port name for PacketOrphaned events (the output port the packet was sent from)."""
687
+ ...
688
+
689
+ class NetActionResponseData:
690
+ """Response data from a successful action.
691
+
692
+ The response type depends on the action:
693
+ - `create_packet()` -> `Packet`
694
+ - `create_epoch()` -> `CreatedEpoch`
695
+ - `start_epoch()` -> `StartedEpoch`
696
+ - `finish_epoch()` -> `FinishedEpoch`
697
+ - `cancel_epoch()` -> `CancelledEpoch`
698
+ - Other actions -> `Empty`
699
+
700
+ Use `isinstance()` or check the type to determine which response you have.
701
+ """
702
+
703
+ class Packet:
704
+ """Response from create_packet()."""
705
+
706
+ packet_id: str
707
+
708
+ class CreatedEpoch:
709
+ """Response from create_epoch(). Epoch is in Startable state."""
710
+
711
+ epoch: Epoch
712
+
713
+ class StartedEpoch:
714
+ """Response from start_epoch(). Epoch is in Running state."""
715
+
716
+ epoch: Epoch
717
+
718
+ class FinishedEpoch:
719
+ """Response from finish_epoch(). Epoch is in Finished state."""
720
+
721
+ epoch: Epoch
722
+
723
+ class CancelledEpoch:
724
+ """Response from cancel_epoch()."""
725
+
726
+ epoch: Epoch
727
+ destroyed_packets: List[str]
728
+
729
+ class Empty:
730
+ """Response from actions that don't return specific data."""
731
+
732
+ pass
733
+
734
+ class NetSim:
735
+ """The runtime state of a flow-based network.
736
+
737
+ NetSim simulates packet flow through a network of nodes. It tracks packet
738
+ locations, validates flow conditions, and manages epoch lifecycles.
739
+
740
+ Example:
741
+ # Create a network
742
+ graph = Graph(nodes=[...], edges=[...])
743
+ net = NetSim(graph)
744
+
745
+ # Create a packet and place it on an edge
746
+ response, events = net.do_action(NetAction.create_packet())
747
+ packet_id = response.packet_id
748
+
749
+ edge = graph.edges()[0]
750
+ net.do_action(NetAction.transport_packet_to_location(
751
+ packet_id,
752
+ PacketLocation.edge(edge)
753
+ ))
754
+
755
+ # Run the network - packet will flow to input ports and trigger epochs
756
+ net.do_action(NetAction.run_step())
757
+
758
+ # Check for startable epochs
759
+ startable = net.get_startable_epochs()
760
+ """
761
+
762
+ def __init__(self, graph: Graph) -> None:
763
+ """Create a new network simulation from a graph topology."""
764
+ ...
765
+
766
+ def do_action(
767
+ self, action: NetAction
768
+ ) -> Tuple[NetActionResponseData, List[NetEvent]]:
769
+ """Execute an action on the network.
770
+
771
+ Args:
772
+ action: The action to perform.
773
+
774
+ Returns:
775
+ A tuple of (response_data, events) where response_data contains
776
+ action-specific results and events is a list of things that happened.
777
+
778
+ Raises:
779
+ Various exceptions depending on the action (see exception classes).
780
+ """
781
+ ...
782
+
783
+ def packet_count_at(self, location: PacketLocation) -> int:
784
+ """Get the number of packets at a location."""
785
+ ...
786
+
787
+ def get_packets_at_location(self, location: PacketLocation) -> List[ULID]:
788
+ """Get the IDs of all packets at a location."""
789
+ ...
790
+
791
+ def get_epoch(self, epoch_id: Union[ULID, str]) -> Optional[Epoch]:
792
+ """Get an epoch by ID, or None if not found."""
793
+ ...
794
+
795
+ def get_startable_epochs(self) -> List[ULID]:
796
+ """Get IDs of all epochs in Startable state."""
797
+ ...
798
+
799
+ def get_packet(self, packet_id: Union[ULID, str]) -> Optional[Packet]:
800
+ """Get a packet by ID, or None if not found."""
801
+ ...
802
+
803
+ @property
804
+ def graph(self) -> Graph:
805
+ """The graph topology this simulation is running on."""
806
+ ...
807
+
808
+ def is_blocked(self) -> bool:
809
+ """Check if the network is blocked (no progress can be made by run_step).
810
+
811
+ Returns True if no packets can move from edges to input ports.
812
+ """
813
+ ...
814
+
815
+ def run_until_blocked(self) -> List[NetEvent]:
816
+ """Run the network until blocked, returning all events.
817
+
818
+ Convenience method that repeatedly calls run_step until no more
819
+ progress can be made. Returns all events that occurred.
820
+ """
821
+ ...
822
+
823
+ def run_step(self) -> Tuple[bool, List[NetEvent]]:
824
+ """Run one step of automatic packet flow.
825
+
826
+ Convenience method equivalent to `do_action(NetAction.run_step())`.
827
+
828
+ Returns:
829
+ A tuple of (made_progress, events) where made_progress indicates
830
+ whether any packets were moved.
831
+ """
832
+ ...
833
+
834
+ def undo_action(self, action: NetAction, events: List[NetEvent]) -> None:
835
+ """Undo a previously executed action.
836
+
837
+ Args:
838
+ action: The original action that was executed.
839
+ events: The events that were produced by the action.
840
+
841
+ Raises:
842
+ UndoError: If the undo operation fails.
843
+ """
844
+ ...
845
+
846
+ # === Re-exports ===
847
+
848
+ __all__ = [
849
+ # Exceptions
850
+ "NetrunError",
851
+ "PacketNotFoundError",
852
+ "EpochNotFoundError",
853
+ "EpochNotRunningError",
854
+ "EpochNotStartableError",
855
+ "CannotFinishNonEmptyEpochError",
856
+ "UnsentOutputSalvoError",
857
+ "PacketNotInNodeError",
858
+ "OutputPortNotFoundError",
859
+ "OutputPortFullError",
860
+ "SalvoConditionNotFoundError",
861
+ "SalvoConditionNotMetError",
862
+ "MaxSalvosExceededError",
863
+ "NodeNotFoundError",
864
+ "PacketNotAtInputPortError",
865
+ "InputPortNotFoundError",
866
+ "InputPortFullError",
867
+ "CannotMovePacketFromRunningEpochError",
868
+ "CannotMovePacketIntoRunningEpochError",
869
+ "EdgeNotFoundError",
870
+ "UnconnectedOutputPortError",
871
+ "GraphValidationError",
872
+ # Graph types
873
+ "PacketCount",
874
+ "PacketCountN",
875
+ "MaxSalvos",
876
+ "MaxSalvosFinite",
877
+ "PortSlotSpec",
878
+ "PortSlotSpecFinite",
879
+ "PortState",
880
+ "PortStateNumeric",
881
+ "SalvoConditionTerm",
882
+ "Port",
883
+ "PortType",
884
+ "PortRef",
885
+ "Edge",
886
+ "SalvoCondition",
887
+ "SalvoConditionPorts",
888
+ "Node",
889
+ "Graph",
890
+ # NetSim types
891
+ "PacketLocation",
892
+ "EpochState",
893
+ "Packet",
894
+ "Salvo",
895
+ "OrphanedPacketInfo",
896
+ "Epoch",
897
+ "NetAction",
898
+ "NetEvent",
899
+ "NetActionResponseData",
900
+ "NetSim",
901
+ ]
Binary file
netrun_sim/py.typed ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: netrun-sim
3
+ Version: 0.1.2
4
+ Classifier: Programming Language :: Rust
5
+ Classifier: Programming Language :: Python :: Implementation :: CPython
6
+ Requires-Dist: python-ulid>=1.0
7
+ Requires-Python: >=3.8
@@ -0,0 +1,7 @@
1
+ netrun_sim/__init__.py,sha256=QUqlsTLVQZBCNn20pZ2JtfmP3R1HJXSBxSG6EF5d8Zo,1291
2
+ netrun_sim/__init__.pyi,sha256=9m3WdQeZVPk7X7RXKMEZl_z3ZKwKi_XLwQsbg0TrSY8,25818
3
+ netrun_sim/netrun_sim.abi3.so,sha256=s2Ca5mYzjkAM7I2ntgTnR3le7Xlgkm7TTM45gtzaQ9E,1490288
4
+ netrun_sim/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ netrun_sim-0.1.2.dist-info/METADATA,sha256=f9TFOJZLbzZOFFP_eTpPhF58YbZpxg07De5YcB52op8,222
6
+ netrun_sim-0.1.2.dist-info/WHEEL,sha256=NDjQq5uVqWLc8zgRoGDmdw4kCR3n_E8rgtwbyW-aNqw,105
7
+ netrun_sim-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp38-abi3-macosx_10_12_x86_64