tinycwrap 0.0.3__tar.gz → 0.0.5__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinycwrap
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Lightweight C-to-Python wrapper generator using CFFI and NumPy
5
5
  Author: TinyCWrap Contributors
6
6
  Requires-Python: >=3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tinycwrap"
7
- version = "0.0.3"
7
+ version = "0.0.5"
8
8
  description = "Lightweight C-to-Python wrapper generator using CFFI and NumPy"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -27,12 +27,18 @@ def test_circle_points_length_contract(cg):
27
27
  assert pts.shape == (101,)
28
28
 
29
29
 
30
+
30
31
  def test_return_struct_array_member(cg):
31
32
  seg = cg.G2DSegment()
32
33
  seg.type = 1
33
34
  seg.data = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0]
34
35
  assert seg.type == 1
35
36
  np.testing.assert_allclose(seg.data, [0.0, 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0])
37
+ seg= cg.G2DSegment(type=2, data=[9.0]*8)
38
+ assert seg.type == 2
39
+ np.testing.assert_allclose(seg.data, [9.0]*8)
40
+
41
+
36
42
 
37
43
  def test_geom2d_return_strct(cg):
38
44
  seg = cg.geom2d_line_segment_from_start_length(0.1, 0.2, 0.5, 0.4, 0.3)
@@ -14,9 +14,9 @@ def cm():
14
14
 
15
15
 
16
16
  def test_dot(cm):
17
- x = np.arange(10, dtype=np.float64)
18
- y = np.ones_like(x)
19
- assert cm.dot(x, y, len_x=len(x)) == np.sum(x * y)
17
+ x = np.array([1.0, 2.0, 3.0], dtype=np.float64)
18
+ y = np.array([4.0, 5.0, 6.0], dtype=np.float64)
19
+ assert cm.dot(x, y, len_x=len(x)) == np.dot(x, y)
20
20
 
21
21
 
22
22
  def test_scale_auto_output(cm):
@@ -39,3 +39,20 @@ def test_return_class_path(cp):
39
39
  seg = path.segments[0]
40
40
  assert seg['type'] == 1 # circle segment
41
41
  np.testing.assert_allclose(seg['data'][:3], [0.0, 0.0, 1.0]) # cx,cy,radius
42
+
43
+ def test_build_pointer_from_data(cp):
44
+ segments=cp.geom2d_rectangle_to_path(2.0, 3.0)
45
+ assert segments.shape == (4,)
46
+ path=cp.G2DPath(segments=segments, len_segments=len(segments))
47
+ assert path.len_segments == 4
48
+ assert path.segments.shape == (4,)
49
+
50
+ def test_repr_pointer_array(cp):
51
+ path = cp.G2DPath(len_segments=2)
52
+ repr_str = repr(path)
53
+ assert "segments=<segments* 0x" in repr_str
54
+ assert "len_segments=2" in repr_str
55
+
56
+ def test_post_contract(cp):
57
+ segments, len_segments = cp.geom2d_segments_from_rectellipse(1,2,3,4)
58
+ assert segments.shape == (4,)
@@ -6,6 +6,6 @@ from .cmodule import CModule
6
6
  try:
7
7
  __version__ = version("tinycwrap")
8
8
  except PackageNotFoundError:
9
- __version__ = "0.0.3"
9
+ __version__ = "0.0.5"
10
10
 
11
11
  __all__ = ["CModule", "__version__"]
@@ -381,24 +381,22 @@ class CModule:
381
381
  fspec.doc = doc
382
382
  contracts = []
383
383
  for line in doc.splitlines():
384
- if "Contract:" in line or "Post-Contract:" in line:
385
- after = (
386
- line.split("Contract:", 1)[1]
387
- if "Contract:" in line
388
- else line.split("Post-Contract:", 1)[1]
389
- )
390
- is_post = "Post-Contract" in line
391
- for part in after.split(";"):
392
- part = part.strip()
393
- if not part:
394
- continue
395
- mlen = re.match(r"len\((\w+)\)\s*=\s*(.+)", part)
396
- if not mlen:
397
- mlen = re.match(r"(\w+)\s*=\s*(.+)", part)
398
- if mlen:
399
- target = mlen.group(1)
400
- expr = mlen.group(2).strip()
401
- contracts.append((target, expr, is_post))
384
+ m_contract = re.search(r"(post-)?contract:\s*(.*)", line, flags=re.IGNORECASE)
385
+ if not m_contract:
386
+ continue
387
+ is_post = m_contract.group(1) is not None
388
+ after = m_contract.group(2)
389
+ for part in after.split(";"):
390
+ part = part.strip()
391
+ if not part:
392
+ continue
393
+ mlen = re.match(r"len\((\w+)\)\s*=\s*(.+)", part)
394
+ if not mlen:
395
+ mlen = re.match(r"(\w+)\s*=\s*(.+)", part)
396
+ if mlen:
397
+ target = mlen.group(1)
398
+ expr = mlen.group(2).strip()
399
+ contracts.append((target, expr, is_post))
402
400
  fspec.contracts = contracts or None
403
401
 
404
402
  def _mark_length_params_from_contracts(self):
@@ -496,7 +494,15 @@ class CModule:
496
494
  target_dtype = _struct_dtypes.get(f.base_type)
497
495
  provided = kwargs.get(f.name)
498
496
  if provided is not None and target_dtype is not None:
499
- arr = np.ascontiguousarray(provided, dtype=target_dtype)
497
+ try:
498
+ arr = np.ascontiguousarray(provided, dtype=target_dtype)
499
+ except ValueError:
500
+ if hasattr(provided, "_data"):
501
+ arr = np.ascontiguousarray(provided._data, dtype=target_dtype)
502
+ elif isinstance(provided, (list, tuple)) and provided and hasattr(provided[0], "_data"):
503
+ arr = np.ascontiguousarray([p._data for p in provided], dtype=target_dtype)
504
+ else:
505
+ raise
500
506
  children[f.name] = arr
501
507
  data[f.name] = arr.ctypes.data
502
508
  if len_field in dtype.names and len_field not in kwargs:
@@ -507,15 +513,51 @@ class CModule:
507
513
  children[f.name] = arr
508
514
  data[f.name] = arr.ctypes.data
509
515
  data[len_field] = llen
516
+ elif provided is not None:
517
+ arr = np.ascontiguousarray(provided)
518
+ children[f.name] = arr
519
+ data[f.name] = arr.ctypes.data
520
+ if len_field in dtype.names and len_field not in kwargs:
521
+ data[len_field] = len(arr)
522
+ elif target_dtype is not None and len_field in dtype.names:
523
+ llen = int(kwargs.get(len_field, 1))
524
+ arr = np.zeros(llen, dtype=target_dtype)
525
+ children[f.name] = arr
526
+ data[f.name] = arr.ctypes.data
527
+ data[len_field] = llen
510
528
  for k in dtype.names:
511
529
  if k in kwargs:
512
- data[k] = kwargs[k]
530
+ val = kwargs[k]
531
+ field_info = dtype.fields[k][0]
532
+ if field_info.shape != ():
533
+ arr = np.asarray(val, dtype=field_info.base)
534
+ if arr.shape != field_info.shape:
535
+ raise ValueError(f"Field {k} expects shape {field_info.shape}, got {arr.shape}")
536
+ np.copyto(data[k], arr)
537
+ else:
538
+ if k in children:
539
+ data[k] = children[k].ctypes.data
540
+ elif isinstance(val, np.ndarray):
541
+ if val.dtype.kind == "V" and val.shape != ():
542
+ arr = np.ascontiguousarray(val)
543
+ data[k] = arr.ctypes.data
544
+ if f"len_{k}" in dtype.names and f"len_{k}" not in kwargs:
545
+ data[f"len_{k}"] = len(arr)
546
+ else:
547
+ data[k] = val
548
+ else:
549
+ data[k] = val
513
550
  object.__setattr__(self, "_data", data)
514
551
  object.__setattr__(self, "_children", children)
515
552
 
516
553
  def __repr__(self):
517
554
  parts_list = []
555
+ pointer_lengths = getattr(self, "_pointer_lengths", {})
518
556
  for name in dtype.names:
557
+ if name in pointer_lengths:
558
+ ptr_val = int(self._data[name])
559
+ parts_list.append(f"{name}=<{name}* 0x{ptr_val:x}>")
560
+ continue
519
561
  val = self._data[name]
520
562
  if val.shape == ():
521
563
  parts_list.append(f"{name}={val.item()!r}")
@@ -533,6 +575,7 @@ class CModule:
533
575
  "zeros": staticmethod(
534
576
  lambda n, _dtype=dtype: np.zeros(n, dtype=_dtype)
535
577
  ),
578
+ "_pointer_lengths": {f.name: f"len_{f.name}" for f in spec.fields if f.is_pointer},
536
579
  }
537
580
 
538
581
  for fname in dtype.names:
@@ -632,20 +675,26 @@ class CModule:
632
675
  struct_names = set(self._struct_specs.keys())
633
676
  params: list[str] = []
634
677
  len_params: list[str] = []
678
+ optional_seen = False
635
679
  for a in fspec.args:
636
680
  if a.is_array_out and a.name.lower().startswith("out"):
637
681
  params.append(f"{a.name}=None")
682
+ optional_seen = True
638
683
  elif a.is_length_param:
639
684
  len_params.append(f"{a.name}=None")
685
+ optional_seen = True
640
686
  elif (
641
687
  a.is_pointer
642
688
  and a.base_type in struct_names
643
689
  and not a.is_const
644
- and a.name.lower().startswith("out")
645
690
  ):
646
691
  params.append(f"{a.name}=None")
692
+ optional_seen = True
647
693
  else:
648
- params.append(a.name)
694
+ if optional_seen:
695
+ params.append(f"{a.name}=None")
696
+ else:
697
+ params.append(a.name)
649
698
 
650
699
  params.extend(len_params)
651
700
  signature = ", ".join(params)
@@ -763,11 +812,10 @@ class CModule:
763
812
  if a.is_array_in:
764
813
  const_prefix = "const " if a.is_const else ""
765
814
  if a.base_type in struct_names:
766
- cls_expr = f"_struct_classes['{a.base_type}']"
767
815
  dtype_expr = f"_struct_dtypes['{a.base_type}']"
768
816
  pre_lines += [
769
- f" if isinstance({a.name}, {cls_expr}):",
770
- f" arr_{a.name} = {a.name}._data",
817
+ f" if hasattr({a.name}, '_data') and getattr({a.name}, '_data', None) is not None and getattr({a.name}, '_data').dtype == {dtype_expr}:",
818
+ f" arr_{a.name} = getattr({a.name}, '_data')",
771
819
  " else:",
772
820
  f" arr_{a.name} = np.ascontiguousarray({a.name}, dtype={dtype_expr})",
773
821
  f" ptr_{a.name} = _self._ffi.cast('{const_prefix}{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
@@ -872,11 +920,10 @@ class CModule:
872
920
  )
873
921
  elif a.is_pointer and a.base_type in struct_names:
874
922
  dtype_expr = f"_struct_dtypes['{a.base_type}']"
875
- cls_expr = f"_struct_classes['{a.base_type}']"
876
923
  if a.is_const:
877
924
  out_lines += [
878
- f" if isinstance({a.name}, {cls_expr}):",
879
- f" arr_{a.name} = {a.name}._data",
925
+ f" if hasattr({a.name}, '_data') and getattr({a.name}, '_data', None) is not None and getattr({a.name}, '_data').dtype == {dtype_expr}:",
926
+ f" arr_{a.name} = getattr({a.name}, '_data')",
880
927
  " else:",
881
928
  f" arr_{a.name} = np.ascontiguousarray({a.name}, dtype={dtype_expr})",
882
929
  f" ptr_{a.name} = _self._ffi.cast('{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
@@ -884,33 +931,18 @@ class CModule:
884
931
  else:
885
932
  out_lines += [
886
933
  f" if {a.name} is None:",
887
- ]
888
- if a.array_len is not None:
889
- out_lines += [
890
- f" arr_{a.name} = np.zeros({int(a.array_len)}, dtype={dtype_expr})"
891
- ]
892
- elif a.name in contract_map:
893
- expr_py = _expr_py(contract_map[a.name])
894
- out_lines += [
895
- f" arr_{a.name} = np.zeros(int({expr_py}), dtype={dtype_expr})"
896
- ]
897
- elif a.array_len is None and a.name not in contract_map:
898
- out_lines += [
899
- f" arr_{a.name} = np.zeros((), dtype={dtype_expr})"
900
- ]
901
- else:
902
- out_lines += [
903
- f" raise ValueError('{fspec.name}: provide {a.name} or a Contract for its length')"
904
- ]
905
- out_lines += [
906
- f" elif isinstance({a.name}, {cls_expr}):",
934
+ f" obj_{a.name} = _struct_classes['{a.base_type}']()",
935
+ f" arr_{a.name} = obj_{a.name}._data",
936
+ f" elif hasattr({a.name}, '_data') and getattr({a.name}, '_data', None) is not None and getattr({a.name}, '_data').dtype == {dtype_expr}:",
937
+ f" obj_{a.name} = {a.name}",
907
938
  f" arr_{a.name} = {a.name}._data",
908
939
  " else:",
909
940
  f" arr_{a.name} = np.ascontiguousarray({a.name}, dtype={dtype_expr})",
941
+ f" obj_{a.name} = None",
910
942
  f" ptr_{a.name} = _self._ffi.cast('{a.base_type} *', _self._ffi.from_buffer(arr_{a.name}))",
911
943
  ]
912
944
  output_names.append(a.name)
913
- output_vars.append(f"arr_{a.name}")
945
+ output_vars.append(f"obj_{a.name} if obj_{a.name} is not None else arr_{a.name}")
914
946
  arg_call_args[idx] = f"ptr_{a.name}"
915
947
  elif a.is_scalar and not a.is_length_param and arg_call_args[idx] is None:
916
948
  scalar_lines.append(f" {a.name} = {a.name}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinycwrap
3
- Version: 0.0.3
3
+ Version: 0.0.5
4
4
  Summary: Lightweight C-to-Python wrapper generator using CFFI and NumPy
5
5
  Author: TinyCWrap Contributors
6
6
  Requires-Python: >=3.9
@@ -1,7 +1,7 @@
1
1
  README.md
2
2
  pyproject.toml
3
- tests/test_examples.py
4
3
  tests/test_geom.py
4
+ tests/test_kernels.py
5
5
  tests/test_path.py
6
6
  tinycwrap/__init__.py
7
7
  tinycwrap/cmodule.py
File without changes
File without changes