flo-python 0.1.0.dev2__py3-none-any.whl → 0.1.0.dev3__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.
flo/wire.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """Flo Wire Protocol
2
2
 
3
3
  Binary serialization/deserialization for the Flo protocol.
4
- Header: 24 bytes, little-endian, CRC32 validated.
4
+ Header: 32 bytes, little-endian, CRC32 validated.
5
5
  """
6
6
 
7
7
  import struct
@@ -34,16 +34,22 @@ from .types import (
34
34
  StatusCode,
35
35
  StorageTier,
36
36
  StreamAppendResult,
37
+ StreamID,
37
38
  StreamInfo,
38
39
  StreamReadResult,
39
40
  StreamRecord,
40
41
  VersionEntry,
41
42
  )
42
43
 
43
- # Header: magic(u32) + payload_len(u32) + request_id(u64)
44
- # + crc32(u32) + version(u8) + status(u8) + flags(u8) + reserved(u8)
45
- REQUEST_HEADER_FORMAT = "<IIQIBBBB"
46
- RESPONSE_HEADER_FORMAT = "<IIQIBBBB"
44
+ # Request header (32 bytes):
45
+ # magic(u32) + payload_len(u32) + request_id(u64)
46
+ # + crc32(u32) + op_code(u16) + version(u8) + flags(u8) + reserved(8s)
47
+ REQUEST_HEADER_FORMAT = "<IIQIHBB8s"
48
+
49
+ # Response header (32 bytes):
50
+ # magic(u32) + data_len(u32) + request_id(u64)
51
+ # + crc32(u32) + version(u8) + status(u8) + flags(u8) + _pad(u8) + reserved(8s)
52
+ RESPONSE_HEADER_FORMAT = "<IIQIBBBB8s"
47
53
 
48
54
 
49
55
  # =============================================================================
@@ -172,11 +178,11 @@ def compute_crc32(header_bytes: bytes, payload: bytes) -> int:
172
178
 
173
179
  The CRC32 is computed over:
174
180
  - Header bytes 0-15 (magic, payload_length, request_id)
175
- - Header bytes 20-23 (version, op_code/status, flags, reserved)
181
+ - Header bytes 20-31 (op_code/version/flags/reserved or version/status/flags/pad/reserved)
176
182
  - Payload bytes
177
183
  """
178
- # Hash bytes 0-15 and 20-23 of header (skip crc32 field at 16-19)
179
- crc_data = header_bytes[0:16] + header_bytes[20:24] + payload
184
+ # Hash bytes 0-15 and 20-31 of header (skip crc32 field at 16-19)
185
+ crc_data = header_bytes[0:16] + header_bytes[20:32] + payload
180
186
  return crc32(crc_data) & 0xFFFFFFFF
181
187
 
182
188
 
@@ -244,10 +250,10 @@ def serialize_request(
244
250
  len(payload_bytes),
245
251
  request_id,
246
252
  0, # CRC32 placeholder
247
- VERSION,
248
253
  op_code,
254
+ VERSION,
249
255
  0, # flags
250
- 0, # reserved
256
+ b"\x00" * 8, # reserved
251
257
  )
252
258
 
253
259
  # Compute CRC32
@@ -260,10 +266,10 @@ def serialize_request(
260
266
  len(payload_bytes),
261
267
  request_id,
262
268
  crc,
263
- VERSION,
264
269
  op_code,
270
+ VERSION,
265
271
  0, # flags
266
- 0, # reserved
272
+ b"\x00" * 8, # reserved
267
273
  )
268
274
 
269
275
  return header + payload_bytes
@@ -309,7 +315,7 @@ def parse_response_header(data: bytes) -> tuple[StatusCode, int, int, int]:
309
315
  if len(data) < HEADER_SIZE:
310
316
  raise IncompleteResponseError(f"Response too short: {len(data)} < {HEADER_SIZE}")
311
317
 
312
- magic, data_len, request_id, crc, version, status, flags, reserved = struct.unpack(
318
+ magic, data_len, request_id, crc, version, status, flags, _pad, reserved = struct.unpack(
313
319
  RESPONSE_HEADER_FORMAT, data[:HEADER_SIZE]
314
320
  )
315
321
 
@@ -538,7 +544,7 @@ def parse_stream_append_response(data: bytes) -> StreamAppendResult:
538
544
  sequence = struct.unpack("<Q", data[0:8])[0]
539
545
  timestamp_ms = struct.unpack("<q", data[8:16])[0]
540
546
 
541
- return StreamAppendResult(sequence=sequence, timestamp_ms=timestamp_ms)
547
+ return StreamAppendResult(id=StreamID(timestamp_ms=timestamp_ms, sequence=sequence))
542
548
 
543
549
 
544
550
  def parse_stream_read_response(data: bytes) -> StreamReadResult:
@@ -619,8 +625,7 @@ def parse_stream_read_response(data: bytes) -> StreamReadResult:
619
625
 
620
626
  records.append(
621
627
  StreamRecord(
622
- sequence=sequence,
623
- timestamp_ms=timestamp_ms,
628
+ id=StreamID(timestamp_ms=timestamp_ms, sequence=sequence),
624
629
  tier=tier,
625
630
  payload=payload,
626
631
  headers=None,
@@ -633,20 +638,23 @@ def parse_stream_read_response(data: bytes) -> StreamReadResult:
633
638
  def parse_stream_info_response(data: bytes) -> StreamInfo:
634
639
  """Parse stream info response data.
635
640
 
636
- Format: [first_seq:u64][last_seq:u64][count:u64][bytes:u64][partition_count:u32]
641
+ Format: [first_ts:u64][first_seq:u64][last_ts:u64][last_seq:u64]
642
+ [count:u64][bytes:u64][partition_count:u32]
637
643
  """
638
- if len(data) < 36:
644
+ if len(data) < 52:
639
645
  raise IncompleteResponseError("Stream info response too short")
640
646
 
641
- first_seq = struct.unpack("<Q", data[0:8])[0]
642
- last_seq = struct.unpack("<Q", data[8:16])[0]
643
- count = struct.unpack("<Q", data[16:24])[0]
644
- bytes_size = struct.unpack("<Q", data[24:32])[0]
645
- partition_count = struct.unpack("<I", data[32:36])[0]
647
+ first_ts = struct.unpack("<Q", data[0:8])[0]
648
+ first_seq = struct.unpack("<Q", data[8:16])[0]
649
+ last_ts = struct.unpack("<Q", data[16:24])[0]
650
+ last_seq = struct.unpack("<Q", data[24:32])[0]
651
+ count = struct.unpack("<Q", data[32:40])[0]
652
+ bytes_size = struct.unpack("<Q", data[40:48])[0]
653
+ partition_count = struct.unpack("<I", data[48:52])[0]
646
654
 
647
655
  return StreamInfo(
648
- first_seq=first_seq,
649
- last_seq=last_seq,
656
+ first_id=StreamID(timestamp_ms=first_ts, sequence=first_seq),
657
+ last_id=StreamID(timestamp_ms=last_ts, sequence=last_seq),
650
658
  count=count,
651
659
  bytes_size=bytes_size,
652
660
  partition_count=partition_count,
@@ -669,10 +677,10 @@ def serialize_group_value(group: str, consumer: str) -> bytes:
669
677
  return bytes(result)
670
678
 
671
679
 
672
- def serialize_group_ack_value(group: str, seqs: list[int], consumer: str = "") -> bytes:
673
- """Serialize group name, consumer, and sequence numbers for group ack/nack.
680
+ def serialize_group_ack_value(group: str, ids: list[StreamID], consumer: str = "") -> bytes:
681
+ """Serialize group name, consumer, and StreamIDs for group ack/nack.
674
682
 
675
- Format: [group_len:u16][group][consumer_len:u16][consumer][count:u32][seq:u64]*
683
+ Format: [group_len:u16][group][consumer_len:u16][consumer][count:u32][ts:u64][seq:u64]*
676
684
  """
677
685
  group_bytes = group.encode("utf-8")
678
686
  consumer_bytes = consumer.encode("utf-8")
@@ -682,9 +690,10 @@ def serialize_group_ack_value(group: str, seqs: list[int], consumer: str = "") -
682
690
  result.extend(group_bytes)
683
691
  result.extend(struct.pack("<H", len(consumer_bytes)))
684
692
  result.extend(consumer_bytes)
685
- result.extend(struct.pack("<I", len(seqs)))
686
- for seq in seqs:
687
- result.extend(struct.pack("<Q", seq))
693
+ result.extend(struct.pack("<I", len(ids)))
694
+ for sid in ids:
695
+ result.extend(struct.pack("<Q", sid.timestamp_ms))
696
+ result.extend(struct.pack("<Q", sid.sequence))
688
697
  return bytes(result)
689
698
 
690
699
 
@@ -703,7 +712,7 @@ def serialize_action_register_value(
703
712
 
704
713
  Format: [action_type:u8][timeout_ms:u32][max_retries:u32]
705
714
  [has_desc:u8][desc_len:u16]?[desc]?
706
- [has_wasm_module:u8]...(all optional fields as u8=0)
715
+ [has_wasm_module:u8=0]...(reserved zero fields for wire compat)
707
716
  """
708
717
  result = bytearray()
709
718
 
@@ -725,13 +734,13 @@ def serialize_action_register_value(
725
734
  else:
726
735
  result.append(0)
727
736
 
728
- # wasm_module (optional, not used)
737
+ # wasm_module (reserved, always zero)
729
738
  result.append(0)
730
739
 
731
- # wasm_entrypoint (optional, not used)
740
+ # wasm_entrypoint (reserved, always zero)
732
741
  result.append(0)
733
742
 
734
- # wasm_memory_limit (optional, not used)
743
+ # wasm_memory_limit (reserved, always zero)
735
744
  result.append(0)
736
745
 
737
746
  # trigger_stream (optional, not used)
@@ -787,24 +796,55 @@ def serialize_action_list_value(limit: int = 100) -> bytes:
787
796
  return struct.pack("<I", limit)
788
797
 
789
798
 
790
- def serialize_worker_register_value(task_types: list[str]) -> bytes:
799
+ def serialize_worker_register_value(
800
+ task_types: list[str],
801
+ *,
802
+ worker_type: int = 0,
803
+ max_concurrency: int = 10,
804
+ metadata: str | None = None,
805
+ machine_id: str | None = None,
806
+ ) -> bytes:
791
807
  """Serialize worker register value.
792
808
 
793
- Format: [count:u32][task_type_len:u16][task_type]...[has_caps:u8][caps]?
809
+ Server format:
810
+ [type:u8][max_concurrency:u32][process_count:u16]
811
+ ([name_len:u16][name][kind:u8])*
812
+ [has_metadata:u8]([metadata_len:u16][metadata])?
813
+ [has_machine_id:u8]([machine_id_len:u16][machine_id])?
794
814
  """
795
815
  result = bytearray()
796
816
 
797
- # count
798
- result.extend(struct.pack("<I", len(task_types)))
817
+ # worker type: 0=action, 1=stream
818
+ result.append(worker_type & 0xFF)
799
819
 
800
- # task types
820
+ # max_concurrency
821
+ result.extend(struct.pack("<I", max_concurrency))
822
+
823
+ # process_count + process entries
824
+ result.extend(struct.pack("<H", len(task_types)))
801
825
  for tt in task_types:
802
826
  tt_bytes = tt.encode("utf-8")
803
827
  result.extend(struct.pack("<H", len(tt_bytes)))
804
828
  result.extend(tt_bytes)
829
+ result.append(worker_type & 0xFF) # kind matches worker type
805
830
 
806
- # capabilities (optional, none)
807
- result.append(0)
831
+ # optional metadata
832
+ if metadata:
833
+ result.append(1)
834
+ meta_bytes = metadata.encode("utf-8")
835
+ result.extend(struct.pack("<H", len(meta_bytes)))
836
+ result.extend(meta_bytes)
837
+ else:
838
+ result.append(0)
839
+
840
+ # optional machine_id
841
+ if machine_id:
842
+ result.append(1)
843
+ mid_bytes = machine_id.encode("utf-8")
844
+ result.extend(struct.pack("<H", len(mid_bytes)))
845
+ result.extend(mid_bytes)
846
+ else:
847
+ result.append(0)
808
848
 
809
849
  return bytes(result)
810
850
 
@@ -828,14 +868,17 @@ def serialize_worker_await_value(task_types: list[str]) -> bytes:
828
868
  return bytes(result)
829
869
 
830
870
 
831
- def serialize_worker_touch_value(task_id: str, extend_ms: int = 30000) -> bytes:
871
+ def serialize_worker_touch_value(action_name: str, task_id: str, extend_ms: int = 30000) -> bytes:
832
872
  """Serialize worker touch value.
833
873
 
834
- Format: [task_id_len:u16][task_id][extend_ms:u32]
874
+ Format: [action_name_len:u16][action_name][task_id_len:u16][task_id][extend_ms:u32]
835
875
  """
876
+ action_bytes = action_name.encode("utf-8")
836
877
  task_id_bytes = task_id.encode("utf-8")
837
878
 
838
879
  result = bytearray()
880
+ result.extend(struct.pack("<H", len(action_bytes)))
881
+ result.extend(action_bytes)
839
882
  result.extend(struct.pack("<H", len(task_id_bytes)))
840
883
  result.extend(task_id_bytes)
841
884
  result.extend(struct.pack("<I", extend_ms))
@@ -843,33 +886,48 @@ def serialize_worker_touch_value(task_id: str, extend_ms: int = 30000) -> bytes:
843
886
  return bytes(result)
844
887
 
845
888
 
846
- def serialize_worker_complete_value(task_id: str, result_data: bytes) -> bytes:
889
+ def serialize_worker_complete_value(
890
+ action_name: str, task_id: str, result_data: bytes, outcome: str = "success"
891
+ ) -> bytes:
847
892
  """Serialize worker complete value.
848
893
 
849
- Format: [task_id_len:u16][task_id][result...]
894
+ Format: [action_name_len:u16][action_name][task_id_len:u16][task_id]
895
+ [outcome_len:u16][outcome][result_len:u16][result]
850
896
  """
897
+ action_bytes = action_name.encode("utf-8")
851
898
  task_id_bytes = task_id.encode("utf-8")
899
+ outcome_bytes = outcome.encode("utf-8")
852
900
 
853
901
  result = bytearray()
902
+ result.extend(struct.pack("<H", len(action_bytes)))
903
+ result.extend(action_bytes)
854
904
  result.extend(struct.pack("<H", len(task_id_bytes)))
855
905
  result.extend(task_id_bytes)
906
+ result.extend(struct.pack("<H", len(outcome_bytes)))
907
+ result.extend(outcome_bytes)
908
+ result.extend(struct.pack("<H", len(result_data)))
856
909
  result.extend(result_data)
857
910
 
858
911
  return bytes(result)
859
912
 
860
913
 
861
- def serialize_worker_fail_value(task_id: str, error_message: str) -> bytes:
914
+ def serialize_worker_fail_value(
915
+ action_name: str, task_id: str, error_message: str, retry: bool = True
916
+ ) -> bytes:
862
917
  """Serialize worker fail value.
863
918
 
864
- Format: [task_id_len:u16][task_id][error_message...]
865
- Note: retry flag is handled via TLV options, not in payload (matches Go SDK).
919
+ Format: [action_name_len:u16][action_name][task_id_len:u16][task_id][retry:u8][error_message...]
866
920
  """
921
+ action_bytes = action_name.encode("utf-8")
867
922
  task_id_bytes = task_id.encode("utf-8")
868
923
  error_bytes = error_message.encode("utf-8")
869
924
 
870
925
  result = bytearray()
926
+ result.extend(struct.pack("<H", len(action_bytes)))
927
+ result.extend(action_bytes)
871
928
  result.extend(struct.pack("<H", len(task_id_bytes)))
872
929
  result.extend(task_id_bytes)
930
+ result.extend(struct.pack("<B", 1 if retry else 0))
873
931
  result.extend(error_bytes)
874
932
 
875
933
  return bytes(result)