tinycwrap 0.0.2__tar.gz → 0.0.3__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.2
3
+ Version: 0.0.3
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.2"
7
+ version = "0.0.3"
8
8
  description = "Lightweight C-to-Python wrapper generator using CFFI and NumPy"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -52,3 +52,12 @@ def test_docstring_contains_contract(cg):
52
52
  doc = cg.geom2d_rectangle_to_path.__doc__
53
53
  assert "Contract: len(out_segments)=4" in doc
54
54
  assert "Auto-wrapped C function `geom2d_rectangle_to_path`." in doc
55
+
56
+
57
+ def test_polygon_length(cg):
58
+ pts = cg.G2DPoints(len_points=3)
59
+ pts.points[0]["x"] = 0.0; pts.points[0]["y"] = 0.0
60
+ pts.points[1]["x"] = 1.0; pts.points[1]["y"] = 0.0
61
+ pts.points[2]["x"] = 1.0; pts.points[2]["y"] = 1.0
62
+ length = cg.geom2d_polygon_length(pts.points)
63
+ assert length == 2.0
@@ -21,3 +21,21 @@ def test_path_get_steps_contract(cp):
21
21
  expected_len = cp.geom2d_path_get_len_steps(segments, len_segments=len(segments), ds_min=0.25)
22
22
  assert len(steps) == expected_len
23
23
  assert np.isclose(steps[-1], cp.geom2d_path_get_length(segments, len_segments=1))
24
+
25
+
26
+ def test_path_struct_pointer_array(cp):
27
+ path = cp.G2DPath(len_segments=3)
28
+ assert path.len_segments == 3
29
+ assert isinstance(path.segments, np.ndarray)
30
+ assert path.segments.shape == (3,)
31
+ assert path.segments.dtype == cp.G2DSegment.dtype
32
+
33
+
34
+ def test_return_class_path(cp):
35
+ path=cp.G2DPath(len_segments=1)
36
+ cp.geom2d_path_from_circle(1.0, path)
37
+ assert path.len_segments == 1
38
+ assert path.segments.shape == (1,)
39
+ seg = path.segments[0]
40
+ assert seg['type'] == 1 # circle segment
41
+ np.testing.assert_allclose(seg['data'][:3], [0.0, 0.0, 1.0]) # cx,cy,radius
@@ -6,6 +6,6 @@ from .cmodule import CModule
6
6
  try:
7
7
  __version__ = version("tinycwrap")
8
8
  except PackageNotFoundError:
9
- __version__ = "0.0.2"
9
+ __version__ = "0.0.3"
10
10
 
11
11
  __all__ = ["CModule", "__version__"]
@@ -462,8 +462,19 @@ class CModule:
462
462
  for sname, sspec in self._struct_specs.items():
463
463
  try:
464
464
  dtype_fields = []
465
+ pointer_meta = []
465
466
  for f in sspec.fields:
466
- base_dtype = numpy_dtype_for_base_type(f.base_type)
467
+ try:
468
+ base_dtype = numpy_dtype_for_base_type(f.base_type)
469
+ except TypeError:
470
+ base_dtype = None
471
+ if f.is_pointer:
472
+ dtype_fields.append((f.name, np.uintp))
473
+ pointer_meta.append(f)
474
+ if base_dtype is None:
475
+ continue
476
+ if base_dtype is None:
477
+ continue
467
478
  if f.array_len:
468
479
  dtype_fields.append((f.name, (base_dtype, (f.array_len,))))
469
480
  else:
@@ -472,15 +483,35 @@ class CModule:
472
483
  except TypeError:
473
484
  continue
474
485
 
475
- def make_struct_class(spec, dtype):
476
- slots = ("_data",)
486
+ def make_struct_class(spec, dtype, _struct_dtypes=self._struct_dtypes):
487
+ slots = ("_data", "_children")
477
488
 
478
489
  def __init__(self, **kwargs):
479
490
  data = np.zeros((), dtype=dtype)
491
+ children = {}
492
+ # handle pointer+len convention
493
+ for f in spec.fields:
494
+ if f.is_pointer and f.name in dtype.names:
495
+ len_field = f"len_{f.name}"
496
+ target_dtype = _struct_dtypes.get(f.base_type)
497
+ provided = kwargs.get(f.name)
498
+ if provided is not None and target_dtype is not None:
499
+ arr = np.ascontiguousarray(provided, dtype=target_dtype)
500
+ children[f.name] = arr
501
+ data[f.name] = arr.ctypes.data
502
+ if len_field in dtype.names and len_field not in kwargs:
503
+ data[len_field] = len(arr)
504
+ elif len_field in kwargs and target_dtype is not None:
505
+ llen = int(kwargs[len_field])
506
+ arr = np.zeros(llen, dtype=target_dtype)
507
+ children[f.name] = arr
508
+ data[f.name] = arr.ctypes.data
509
+ data[len_field] = llen
480
510
  for k in dtype.names:
481
511
  if k in kwargs:
482
512
  data[k] = kwargs[k]
483
513
  object.__setattr__(self, "_data", data)
514
+ object.__setattr__(self, "_children", children)
484
515
 
485
516
  def __repr__(self):
486
517
  parts_list = []
@@ -508,7 +539,9 @@ class CModule:
508
539
  field_info = dtype.fields[fname][0]
509
540
  is_scalar = field_info.shape == ()
510
541
 
511
- def getter(self, fname=fname, is_scalar=is_scalar):
542
+ def getter(self, fname=fname, is_scalar=is_scalar, field_info=field_info):
543
+ if fname in getattr(self, "_children", {}):
544
+ return self._children[fname]
512
545
  val = self._data[fname]
513
546
  return val.item() if is_scalar else val
514
547
 
@@ -519,6 +552,11 @@ class CModule:
519
552
  field_info=field_info,
520
553
  is_scalar=is_scalar,
521
554
  ):
555
+ if fname in getattr(self, "_children", {}):
556
+ arr = np.ascontiguousarray(value, dtype=self._children[fname].dtype)
557
+ self._children[fname] = arr
558
+ self._data[fname] = arr.ctypes.data
559
+ return
522
560
  if is_scalar:
523
561
  self._data[fname] = value
524
562
  else:
@@ -200,10 +200,6 @@ def _parse_structs_with_pycparser(cdef: str) -> dict[str, StructSpec]:
200
200
  for decl in struct.decls:
201
201
  ctype, is_ptr, is_const, arr_len = _ctype_from_decl(decl.type)
202
202
  base = base_type_from_ctype(ctype)
203
- try:
204
- numpy_dtype_for_base_type(base)
205
- except TypeError:
206
- continue
207
203
  fields.append(
208
204
  StructField(
209
205
  name=decl.name,
@@ -311,7 +307,7 @@ def _parse_structs_regex(cdef: str) -> dict[str, StructSpec]:
311
307
  if not line:
312
308
  continue
313
309
  line = re.sub(r"/\*.*?\*/", "", line).strip()
314
- m_field = re.match(r"(.+?)\s+([A-Za-z_]\w*)(\s*\[(\d+)\])?$", line)
310
+ m_field = re.match(r"(.+?\*?)\s*([A-Za-z_]\w*)(\s*\[(\d+)\])?$", line)
315
311
  if not m_field:
316
312
  continue
317
313
  raw_ctype = m_field.group(1).strip()
@@ -320,10 +316,6 @@ def _parse_structs_regex(cdef: str) -> dict[str, StructSpec]:
320
316
  is_pointer = "*" in raw_ctype
321
317
  is_const = "const" in raw_ctype
322
318
  base = base_type_from_ctype(raw_ctype)
323
- try:
324
- numpy_dtype_for_base_type(base)
325
- except TypeError:
326
- continue
327
319
  fields.append(
328
320
  StructField(
329
321
  name=fname,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinycwrap
3
- Version: 0.0.2
3
+ Version: 0.0.3
4
4
  Summary: Lightweight C-to-Python wrapper generator using CFFI and NumPy
5
5
  Author: TinyCWrap Contributors
6
6
  Requires-Python: >=3.9
File without changes
File without changes