cocoindex 0.1.50__cp312-cp312-macosx_11_0_arm64.whl → 0.1.52__cp312-cp312-macosx_11_0_arm64.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.
@@ -1,12 +1,14 @@
1
1
  import uuid
2
2
  import datetime
3
3
  from dataclasses import dataclass, make_dataclass
4
- from typing import NamedTuple, Literal, Any, Callable
4
+ from typing import NamedTuple, Literal, Any, Callable, Union
5
5
  import pytest
6
6
  import cocoindex
7
7
  from cocoindex.typing import (
8
8
  encode_enriched_type,
9
9
  Vector,
10
+ Float32,
11
+ Float64,
10
12
  )
11
13
  from cocoindex.convert import (
12
14
  encode_engine_value,
@@ -32,7 +34,7 @@ class Tag:
32
34
 
33
35
  @dataclass
34
36
  class Basket:
35
- items: list
37
+ items: list[str]
36
38
 
37
39
 
38
40
  @dataclass
@@ -74,39 +76,50 @@ def build_engine_value_decoder(
74
76
 
75
77
 
76
78
  def validate_full_roundtrip(
77
- value: Any, output_type: Any, input_type: Any | None = None
79
+ value: Any,
80
+ value_type: Any = None,
81
+ *other_decoded_values: tuple[Any, Any],
78
82
  ) -> None:
79
83
  """
80
84
  Validate the given value doesn't change after encoding, sending to engine (using output_type), receiving back and decoding (using input_type).
81
85
 
82
- If `input_type` is not specified, uses `output_type` as the target.
86
+ `other_decoded_values` is a tuple of (value, type) pairs.
87
+ If provided, also validate the value can be decoded to the other types.
83
88
  """
84
- from cocoindex import _engine
89
+ from cocoindex import _engine # type: ignore
85
90
 
86
91
  encoded_value = encode_engine_value(value)
87
- encoded_output_type = encode_enriched_type(output_type)["type"]
92
+ value_type = value_type or type(value)
93
+ encoded_output_type = encode_enriched_type(value_type)["type"]
88
94
  value_from_engine = _engine.testutil.seder_roundtrip(
89
95
  encoded_value, encoded_output_type
90
96
  )
91
- decoded_value = build_engine_value_decoder(input_type or output_type, output_type)(
97
+ decoded_value = build_engine_value_decoder(value_type, value_type)(
92
98
  value_from_engine
93
99
  )
94
- assert decoded_value == value
100
+ np.testing.assert_array_equal(decoded_value, value)
95
101
 
102
+ if other_decoded_values is not None:
103
+ for other_value, other_type in other_decoded_values:
104
+ other_decoded_value = build_engine_value_decoder(other_type, other_type)(
105
+ value_from_engine
106
+ )
107
+ np.testing.assert_array_equal(other_decoded_value, other_value)
96
108
 
97
- def test_encode_engine_value_basic_types():
109
+
110
+ def test_encode_engine_value_basic_types() -> None:
98
111
  assert encode_engine_value(123) == 123
99
112
  assert encode_engine_value(3.14) == 3.14
100
113
  assert encode_engine_value("hello") == "hello"
101
114
  assert encode_engine_value(True) is True
102
115
 
103
116
 
104
- def test_encode_engine_value_uuid():
117
+ def test_encode_engine_value_uuid() -> None:
105
118
  u = uuid.uuid4()
106
119
  assert encode_engine_value(u) == u.bytes
107
120
 
108
121
 
109
- def test_encode_engine_value_date_time_types():
122
+ def test_encode_engine_value_date_time_types() -> None:
110
123
  d = datetime.date(2024, 1, 1)
111
124
  assert encode_engine_value(d) == d
112
125
  t = datetime.time(12, 30)
@@ -115,7 +128,7 @@ def test_encode_engine_value_date_time_types():
115
128
  assert encode_engine_value(dt) == dt
116
129
 
117
130
 
118
- def test_encode_engine_value_struct():
131
+ def test_encode_engine_value_struct() -> None:
119
132
  order = Order(order_id="O123", name="mixed nuts", price=25.0)
120
133
  assert encode_engine_value(order) == ["O123", "mixed nuts", 25.0, "default_extra"]
121
134
 
@@ -128,7 +141,7 @@ def test_encode_engine_value_struct():
128
141
  ]
129
142
 
130
143
 
131
- def test_encode_engine_value_list_of_structs():
144
+ def test_encode_engine_value_list_of_structs() -> None:
132
145
  orders = [Order("O1", "item1", 10.0), Order("O2", "item2", 20.0)]
133
146
  assert encode_engine_value(orders) == [
134
147
  ["O1", "item1", 10.0, "default_extra"],
@@ -145,12 +158,12 @@ def test_encode_engine_value_list_of_structs():
145
158
  ]
146
159
 
147
160
 
148
- def test_encode_engine_value_struct_with_list():
161
+ def test_encode_engine_value_struct_with_list() -> None:
149
162
  basket = Basket(items=["apple", "banana"])
150
163
  assert encode_engine_value(basket) == [["apple", "banana"]]
151
164
 
152
165
 
153
- def test_encode_engine_value_nested_struct():
166
+ def test_encode_engine_value_nested_struct() -> None:
154
167
  customer = Customer(name="Alice", order=Order("O1", "item1", 10.0))
155
168
  assert encode_engine_value(customer) == [
156
169
  "Alice",
@@ -168,12 +181,12 @@ def test_encode_engine_value_nested_struct():
168
181
  ]
169
182
 
170
183
 
171
- def test_encode_engine_value_empty_list():
184
+ def test_encode_engine_value_empty_list() -> None:
172
185
  assert encode_engine_value([]) == []
173
186
  assert encode_engine_value([[]]) == [[]]
174
187
 
175
188
 
176
- def test_encode_engine_value_tuple():
189
+ def test_encode_engine_value_tuple() -> None:
177
190
  assert encode_engine_value(()) == []
178
191
  assert encode_engine_value((1, 2, 3)) == [1, 2, 3]
179
192
  assert encode_engine_value(((1, 2), (3, 4))) == [[1, 2], [3, 4]]
@@ -181,20 +194,23 @@ def test_encode_engine_value_tuple():
181
194
  assert encode_engine_value(((),)) == [[]]
182
195
 
183
196
 
184
- def test_encode_engine_value_none():
197
+ def test_encode_engine_value_none() -> None:
185
198
  assert encode_engine_value(None) is None
186
199
 
187
200
 
188
- def test_make_engine_value_decoder_basic_types():
189
- for engine_type_in_py, value in [
190
- (int, 42),
191
- (float, 3.14),
192
- (str, "hello"),
193
- (bool, True),
194
- # (type(None), None), # Removed unsupported NoneType
195
- ]:
196
- decoder = build_engine_value_decoder(engine_type_in_py)
197
- assert decoder(value) == value
201
+ def test_roundtrip_basic_types() -> None:
202
+ validate_full_roundtrip(42, int)
203
+ validate_full_roundtrip(3.25, float, (3.25, Float64))
204
+ validate_full_roundtrip(3.25, Float64, (3.25, float))
205
+ validate_full_roundtrip(3.25, Float32)
206
+ validate_full_roundtrip("hello", str)
207
+ validate_full_roundtrip(True, bool)
208
+ validate_full_roundtrip(False, bool)
209
+ validate_full_roundtrip(datetime.date(2025, 1, 1), datetime.date)
210
+ validate_full_roundtrip(datetime.datetime.now(), cocoindex.LocalDateTime)
211
+ validate_full_roundtrip(
212
+ datetime.datetime.now(datetime.UTC), cocoindex.OffsetDateTime
213
+ )
198
214
 
199
215
 
200
216
  @pytest.mark.parametrize(
@@ -312,18 +328,18 @@ def test_make_engine_value_decoder_basic_types():
312
328
  ),
313
329
  ],
314
330
  )
315
- def test_struct_decoder_cases(data_type, engine_val, expected):
331
+ def test_struct_decoder_cases(data_type: Any, engine_val: Any, expected: Any) -> None:
316
332
  decoder = build_engine_value_decoder(data_type)
317
333
  assert decoder(engine_val) == expected
318
334
 
319
335
 
320
- def test_make_engine_value_decoder_collections():
336
+ def test_make_engine_value_decoder_list_of_struct() -> None:
321
337
  # List of structs (dataclass)
322
- decoder = build_engine_value_decoder(list[Order])
323
338
  engine_val = [
324
339
  ["O1", "item1", 10.0, "default_extra"],
325
340
  ["O2", "item2", 20.0, "default_extra"],
326
341
  ]
342
+ decoder = build_engine_value_decoder(list[Order])
327
343
  assert decoder(engine_val) == [
328
344
  Order("O1", "item1", 10.0, "default_extra"),
329
345
  Order("O2", "item2", 20.0, "default_extra"),
@@ -336,13 +352,15 @@ def test_make_engine_value_decoder_collections():
336
352
  OrderNamedTuple("O2", "item2", 20.0, "default_extra"),
337
353
  ]
338
354
 
355
+
356
+ def test_make_engine_value_decoder_struct_of_list() -> None:
339
357
  # Struct with list field
340
- decoder = build_engine_value_decoder(Customer)
341
358
  engine_val = [
342
359
  "Alice",
343
360
  ["O1", "item1", 10.0, "default_extra"],
344
361
  [["vip"], ["premium"]],
345
362
  ]
363
+ decoder = build_engine_value_decoder(Customer)
346
364
  assert decoder(engine_val) == Customer(
347
365
  "Alice",
348
366
  Order("O1", "item1", 10.0, "default_extra"),
@@ -357,8 +375,9 @@ def test_make_engine_value_decoder_collections():
357
375
  [Tag("vip"), Tag("premium")],
358
376
  )
359
377
 
378
+
379
+ def test_make_engine_value_decoder_struct_of_struct() -> None:
360
380
  # Struct with struct field
361
- decoder = build_engine_value_decoder(NestedStruct)
362
381
  engine_val = [
363
382
  ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]]],
364
383
  [
@@ -367,6 +386,7 @@ def test_make_engine_value_decoder_collections():
367
386
  ],
368
387
  2,
369
388
  ]
389
+ decoder = build_engine_value_decoder(NestedStruct)
370
390
  assert decoder(engine_val) == NestedStruct(
371
391
  Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")]),
372
392
  [
@@ -377,11 +397,13 @@ def test_make_engine_value_decoder_collections():
377
397
  )
378
398
 
379
399
 
380
- def make_engine_order(fields):
400
+ def make_engine_order(fields: list[tuple[str, type]]) -> type:
381
401
  return make_dataclass("EngineOrder", fields)
382
402
 
383
403
 
384
- def make_python_order(fields, defaults=None):
404
+ def make_python_order(
405
+ fields: list[tuple[str, type]], defaults: dict[str, Any] | None = None
406
+ ) -> type:
385
407
  if defaults is None:
386
408
  defaults = {}
387
409
  # Move all fields with defaults to the end (Python dataclass requirement)
@@ -455,8 +477,12 @@ def make_python_order(fields, defaults=None):
455
477
  ],
456
478
  )
457
479
  def test_field_position_cases(
458
- engine_fields, python_fields, python_defaults, engine_val, expected_python_val
459
- ):
480
+ engine_fields: list[tuple[str, type]],
481
+ python_fields: list[tuple[str, type]],
482
+ python_defaults: dict[str, Any],
483
+ engine_val: list[Any],
484
+ expected_python_val: tuple[Any, ...],
485
+ ) -> None:
460
486
  EngineOrder = make_engine_order(engine_fields)
461
487
  PythonOrder = make_python_order(python_fields, python_defaults)
462
488
  decoder = build_engine_value_decoder(EngineOrder, PythonOrder)
@@ -513,13 +539,13 @@ def test_roundtrip_ktable_struct_key() -> None:
513
539
  validate_full_roundtrip(value_nt, t_nt)
514
540
 
515
541
 
516
- IntVectorType = cocoindex.Vector[np.int32, Literal[5]]
542
+ IntVectorType = cocoindex.Vector[np.int64, Literal[5]]
517
543
 
518
544
 
519
545
  def test_vector_as_vector() -> None:
520
- value: IntVectorType = [1, 2, 3, 4, 5]
546
+ value = np.array([1, 2, 3, 4, 5], dtype=np.int64)
521
547
  encoded = encode_engine_value(value)
522
- assert encoded == [1, 2, 3, 4, 5]
548
+ assert np.array_equal(encoded, value)
523
549
  decoded = build_engine_value_decoder(IntVectorType)(encoded)
524
550
  assert np.array_equal(decoded, value)
525
551
 
@@ -540,12 +566,17 @@ Float32VectorType = Vector[np.float32, Literal[3]]
540
566
  Float64VectorType = Vector[np.float64, Literal[3]]
541
567
  Int64VectorType = Vector[np.int64, Literal[3]]
542
568
  Int32VectorType = Vector[np.int32, Literal[3]]
569
+ UInt8VectorType = Vector[np.uint8, Literal[3]]
570
+ UInt16VectorType = Vector[np.uint16, Literal[3]]
571
+ UInt32VectorType = Vector[np.uint32, Literal[3]]
572
+ UInt64VectorType = Vector[np.uint64, Literal[3]]
573
+ StrVectorType = Vector[str]
543
574
  NDArrayFloat32Type = NDArray[np.float32]
544
575
  NDArrayFloat64Type = NDArray[np.float64]
545
576
  NDArrayInt64Type = NDArray[np.int64]
546
577
 
547
578
 
548
- def test_encode_engine_value_ndarray():
579
+ def test_encode_engine_value_ndarray() -> None:
549
580
  """Test encoding NDArray vectors to lists for the Rust engine."""
550
581
  vec_f32: Float32VectorType = np.array([1.0, 2.0, 3.0], dtype=np.float32)
551
582
  assert np.array_equal(encode_engine_value(vec_f32), [1.0, 2.0, 3.0])
@@ -557,7 +588,7 @@ def test_encode_engine_value_ndarray():
557
588
  assert np.array_equal(encode_engine_value(vec_nd_f32), [1.0, 2.0, 3.0])
558
589
 
559
590
 
560
- def test_make_engine_value_decoder_ndarray():
591
+ def test_make_engine_value_decoder_ndarray() -> None:
561
592
  """Test decoding engine lists to NDArray vectors."""
562
593
  decoder_f32 = build_engine_value_decoder(Float32VectorType)
563
594
  result_f32 = decoder_f32([1.0, 2.0, 3.0])
@@ -581,16 +612,16 @@ def test_make_engine_value_decoder_ndarray():
581
612
  assert np.array_equal(result_nd_f32, np.array([1.0, 2.0, 3.0], dtype=np.float32))
582
613
 
583
614
 
584
- def test_roundtrip_ndarray_vector():
615
+ def test_roundtrip_ndarray_vector() -> None:
585
616
  """Test roundtrip encoding and decoding of NDArray vectors."""
586
- value_f32: Float32VectorType = np.array([1.0, 2.0, 3.0], dtype=np.float32)
617
+ value_f32 = np.array([1.0, 2.0, 3.0], dtype=np.float32)
587
618
  encoded_f32 = encode_engine_value(value_f32)
588
619
  np.array_equal(encoded_f32, [1.0, 2.0, 3.0])
589
620
  decoded_f32 = build_engine_value_decoder(Float32VectorType)(encoded_f32)
590
621
  assert isinstance(decoded_f32, np.ndarray)
591
622
  assert decoded_f32.dtype == np.float32
592
623
  assert np.array_equal(decoded_f32, value_f32)
593
- value_i64: Int64VectorType = np.array([1, 2, 3], dtype=np.int64)
624
+ value_i64 = np.array([1, 2, 3], dtype=np.int64)
594
625
  encoded_i64 = encode_engine_value(value_i64)
595
626
  assert np.array_equal(encoded_i64, [1, 2, 3])
596
627
  decoded_i64 = build_engine_value_decoder(Int64VectorType)(encoded_i64)
@@ -606,63 +637,23 @@ def test_roundtrip_ndarray_vector():
606
637
  assert np.array_equal(decoded_nd_f64, value_nd_f64)
607
638
 
608
639
 
609
- def test_uint_support():
610
- """Test encoding and decoding of unsigned integer vectors."""
611
- value_uint8 = np.array([1, 2, 3, 4], dtype=np.uint8)
612
- encoded = encode_engine_value(value_uint8)
613
- assert np.array_equal(encoded, [1, 2, 3, 4])
614
- decoder = make_engine_value_decoder(
615
- [], {"kind": "Vector", "element_type": {"kind": "UInt8"}}, NDArray[np.uint8]
616
- )
617
- decoded = decoder(encoded)
618
- assert np.array_equal(decoded, value_uint8)
619
- assert decoded.dtype == np.uint8
620
- value_uint16 = np.array([1, 2, 3, 4], dtype=np.uint16)
621
- encoded = encode_engine_value(value_uint16)
622
- assert np.array_equal(encoded, [1, 2, 3, 4])
623
- decoder = make_engine_value_decoder(
624
- [], {"kind": "Vector", "element_type": {"kind": "UInt16"}}, NDArray[np.uint16]
625
- )
626
- decoded = decoder(encoded)
627
- assert np.array_equal(decoded, value_uint16)
628
- assert decoded.dtype == np.uint16
629
- value_uint32 = np.array([1, 2, 3], dtype=np.uint32)
630
- encoded = encode_engine_value(value_uint32)
631
- assert np.array_equal(encoded, [1, 2, 3])
632
- decoder = make_engine_value_decoder(
633
- [], {"kind": "Vector", "element_type": {"kind": "UInt32"}}, NDArray[np.uint32]
634
- )
635
- decoded = decoder(encoded)
636
- assert np.array_equal(decoded, value_uint32)
637
- assert decoded.dtype == np.uint32
638
- value_uint64 = np.array([1, 2, 3], dtype=np.uint64)
639
- encoded = encode_engine_value(value_uint64)
640
- assert np.array_equal(encoded, [1, 2, 3])
641
- decoder = make_engine_value_decoder(
642
- [], {"kind": "Vector", "element_type": {"kind": "UInt8"}}, NDArray[np.uint64]
643
- )
644
- decoded = decoder(encoded)
645
- assert np.array_equal(decoded, value_uint64)
646
- assert decoded.dtype == np.uint64
647
-
648
-
649
- def test_ndarray_dimension_mismatch():
640
+ def test_ndarray_dimension_mismatch() -> None:
650
641
  """Test dimension enforcement for Vector with specified dimension."""
651
- value: Float32VectorType = np.array([1.0, 2.0], dtype=np.float32)
642
+ value = np.array([1.0, 2.0], dtype=np.float32)
652
643
  encoded = encode_engine_value(value)
653
644
  assert np.array_equal(encoded, [1.0, 2.0])
654
645
  with pytest.raises(ValueError, match="Vector dimension mismatch"):
655
646
  build_engine_value_decoder(Float32VectorType)(encoded)
656
647
 
657
648
 
658
- def test_list_vector_backward_compatibility():
649
+ def test_list_vector_backward_compatibility() -> None:
659
650
  """Test that list-based vectors still work for backward compatibility."""
660
- value: IntVectorType = [1, 2, 3, 4, 5]
651
+ value = [1, 2, 3, 4, 5]
661
652
  encoded = encode_engine_value(value)
662
653
  assert encoded == [1, 2, 3, 4, 5]
663
654
  decoded = build_engine_value_decoder(IntVectorType)(encoded)
664
655
  assert isinstance(decoded, np.ndarray)
665
- assert decoded.dtype == np.int32
656
+ assert decoded.dtype == np.int64
666
657
  assert np.array_equal(decoded, np.array([1, 2, 3, 4, 5], dtype=np.int64))
667
658
  value_list: ListIntType = [1, 2, 3, 4, 5]
668
659
  encoded = encode_engine_value(value_list)
@@ -671,7 +662,7 @@ def test_list_vector_backward_compatibility():
671
662
  assert np.array_equal(decoded, [1, 2, 3, 4, 5])
672
663
 
673
664
 
674
- def test_encode_complex_structure_with_ndarray():
665
+ def test_encode_complex_structure_with_ndarray() -> None:
675
666
  """Test encoding a complex structure that includes an NDArray."""
676
667
 
677
668
  @dataclass
@@ -684,17 +675,13 @@ def test_encode_complex_structure_with_ndarray():
684
675
  name="test_np", data=np.array([1.0, 0.5], dtype=np.float32), value=100
685
676
  )
686
677
  encoded = encode_engine_value(original)
687
- expected = [
688
- "test_np",
689
- [1.0, 0.5],
690
- 100,
691
- ]
692
- assert encoded[0] == expected[0]
693
- assert np.array_equal(encoded[1], expected[1])
694
- assert encoded[2] == expected[2]
678
+
679
+ assert encoded[0] == original.name
680
+ assert np.array_equal(encoded[1], original.data)
681
+ assert encoded[2] == original.value
695
682
 
696
683
 
697
- def test_decode_nullable_ndarray_none_or_value_input():
684
+ def test_decode_nullable_ndarray_none_or_value_input() -> None:
698
685
  """Test decoding a nullable NDArray with None or value inputs."""
699
686
  src_type_dict = {
700
687
  "kind": "Vector",
@@ -718,7 +705,7 @@ def test_decode_nullable_ndarray_none_or_value_input():
718
705
  )
719
706
 
720
707
 
721
- def test_decode_vector_string():
708
+ def test_decode_vector_string() -> None:
722
709
  """Test decoding a vector of strings works for Python native list type."""
723
710
  src_type_dict = {
724
711
  "kind": "Vector",
@@ -729,7 +716,7 @@ def test_decode_vector_string():
729
716
  assert decoder(["hello", "world"]) == ["hello", "world"]
730
717
 
731
718
 
732
- def test_decode_error_non_nullable_or_non_list_vector():
719
+ def test_decode_error_non_nullable_or_non_list_vector() -> None:
733
720
  """Test decoding errors for non-nullable vectors or non-list inputs."""
734
721
  src_type_dict = {
735
722
  "kind": "Vector",
@@ -743,7 +730,7 @@ def test_decode_error_non_nullable_or_non_list_vector():
743
730
  decoder("not a list")
744
731
 
745
732
 
746
- def test_dump_vector_type_annotation_with_dim():
733
+ def test_dump_vector_type_annotation_with_dim() -> None:
747
734
  """Test dumping a vector type annotation with a specified dimension."""
748
735
  expected_dump = {
749
736
  "type": {
@@ -755,7 +742,7 @@ def test_dump_vector_type_annotation_with_dim():
755
742
  assert dump_engine_object(Float32VectorType) == expected_dump
756
743
 
757
744
 
758
- def test_dump_vector_type_annotation_no_dim():
745
+ def test_dump_vector_type_annotation_no_dim() -> None:
759
746
  """Test dumping a vector type annotation with no dimension."""
760
747
  expected_dump_no_dim = {
761
748
  "type": {
@@ -765,3 +752,63 @@ def test_dump_vector_type_annotation_no_dim():
765
752
  }
766
753
  }
767
754
  assert dump_engine_object(Float64VectorTypeNoDim) == expected_dump_no_dim
755
+
756
+
757
+ def test_full_roundtrip_vector_numeric_types() -> None:
758
+ """Test full roundtrip for numeric vector types using NDArray."""
759
+ value_f32: Vector[np.float32, Literal[3]] = np.array(
760
+ [1.0, 2.0, 3.0], dtype=np.float32
761
+ )
762
+ validate_full_roundtrip(value_f32, Vector[np.float32, Literal[3]])
763
+ value_f64: Vector[np.float64, Literal[3]] = np.array(
764
+ [1.0, 2.0, 3.0], dtype=np.float64
765
+ )
766
+ validate_full_roundtrip(value_f64, Vector[np.float64, Literal[3]])
767
+ value_i64: Vector[np.int64, Literal[3]] = np.array([1, 2, 3], dtype=np.int64)
768
+ validate_full_roundtrip(value_i64, Vector[np.int64, Literal[3]])
769
+ value_i32: Vector[np.int32, Literal[3]] = np.array([1, 2, 3], dtype=np.int32)
770
+ with pytest.raises(ValueError, match="type unsupported yet"):
771
+ validate_full_roundtrip(value_i32, Vector[np.int32, Literal[3]])
772
+ value_u8: Vector[np.uint8, Literal[3]] = np.array([1, 2, 3], dtype=np.uint8)
773
+ with pytest.raises(ValueError, match="type unsupported yet"):
774
+ validate_full_roundtrip(value_u8, Vector[np.uint8, Literal[3]])
775
+ value_u16: Vector[np.uint16, Literal[3]] = np.array([1, 2, 3], dtype=np.uint16)
776
+ with pytest.raises(ValueError, match="type unsupported yet"):
777
+ validate_full_roundtrip(value_u16, Vector[np.uint16, Literal[3]])
778
+ value_u32: Vector[np.uint32, Literal[3]] = np.array([1, 2, 3], dtype=np.uint32)
779
+ with pytest.raises(ValueError, match="type unsupported yet"):
780
+ validate_full_roundtrip(value_u32, Vector[np.uint32, Literal[3]])
781
+ value_u64: Vector[np.uint64, Literal[3]] = np.array([1, 2, 3], dtype=np.uint64)
782
+ with pytest.raises(ValueError, match="type unsupported yet"):
783
+ validate_full_roundtrip(value_u64, Vector[np.uint64, Literal[3]])
784
+
785
+
786
+ def test_roundtrip_vector_no_dimension() -> None:
787
+ """Test full roundtrip for vector types without dimension annotation."""
788
+ value_f64: Vector[np.float64] = np.array([1.0, 2.0, 3.0], dtype=np.float64)
789
+ validate_full_roundtrip(value_f64, Vector[np.float64])
790
+
791
+
792
+ def test_roundtrip_string_vector() -> None:
793
+ """Test full roundtrip for string vector using list."""
794
+ value_str: Vector[str] = ["hello", "world"]
795
+ validate_full_roundtrip(value_str, Vector[str])
796
+
797
+
798
+ def test_roundtrip_empty_vector() -> None:
799
+ """Test full roundtrip for empty numeric vector."""
800
+ value_empty: Vector[np.float32] = np.array([], dtype=np.float32)
801
+ validate_full_roundtrip(value_empty, Vector[np.float32])
802
+
803
+
804
+ def test_roundtrip_dimension_mismatch() -> None:
805
+ """Test that dimension mismatch raises an error during roundtrip."""
806
+ value_f32: Vector[np.float32, Literal[3]] = np.array([1.0, 2.0], dtype=np.float32)
807
+ with pytest.raises(ValueError, match="Vector dimension mismatch"):
808
+ validate_full_roundtrip(value_f32, Vector[np.float32, Literal[3]])
809
+
810
+
811
+ def test_roundtrip_list_backward_compatibility() -> None:
812
+ """Test full roundtrip for list-based vectors for backward compatibility."""
813
+ value_list: list[int] = [1, 2, 3]
814
+ validate_full_roundtrip(value_list, list[int])