cbridge 0.1.0__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.
- cbridge-0.1.0/PKG-INFO +205 -0
- cbridge-0.1.0/README.md +182 -0
- cbridge-0.1.0/pyproject.toml +78 -0
- cbridge-0.1.0/src/cbridge/__init__.py +12 -0
- cbridge-0.1.0/src/cbridge/cbridge.py +112 -0
- cbridge-0.1.0/src/cbridge/cfunc.py +45 -0
- cbridge-0.1.0/src/cbridge/py.typed +0 -0
- cbridge-0.1.0/src/cbridge/types.py +134 -0
cbridge-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cbridge
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Creating ctype structure like dataclass
|
|
5
|
+
Author: ZhengYu, Xu
|
|
6
|
+
Author-email: ZhengYu, Xu <zen-xu@outlook.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Requires-Dist: typing-extensions ; python_full_version < '3.12'
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Project-URL: homepage, https://github.com/zen-xu/cbridge
|
|
20
|
+
Project-URL: issues, https://github.com/zen-xu/cbridge/issues
|
|
21
|
+
Project-URL: repository, https://github.com/zen-xu/cbridge.git
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# cbridge
|
|
25
|
+
|
|
26
|
+
[](https://github.com/astral-sh/uv)
|
|
27
|
+
[](https://github.com/astral-sh/ruff)
|
|
28
|
+

|
|
29
|
+

|
|
30
|
+

|
|
31
|
+
[](https://github.com/zen-xu/cbridge/actions/workflows/test.yaml)
|
|
32
|
+
[](https://codecov.io/gh/zen-xu/cbridge)
|
|
33
|
+
|
|
34
|
+
A Python library that provides a dataclass-like interface for creating ctypes structures, making it easier to work with C libraries and data structures in Python.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- **Dataclass-like syntax**: Define C structures using familiar Python class syntax with type hints
|
|
39
|
+
- **Automatic field management**: Fields are automatically converted to ctypes types based on type annotations
|
|
40
|
+
- **Array support**: Built-in support for fixed-size arrays with proper type checking
|
|
41
|
+
- **Pointer support**: Easy handling of C pointers with type safety
|
|
42
|
+
- **Default values**: Support for default field values and factories
|
|
43
|
+
- **Inheritance**: Full support for class inheritance in C structures
|
|
44
|
+
- **Memory layout control**: Control structure packing and alignment
|
|
45
|
+
- **C function binding**: Decorator for easy binding to C library functions
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install cbridge
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Creating C Structures
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from cbridge import CStruct
|
|
59
|
+
from cbridge import types
|
|
60
|
+
|
|
61
|
+
class Point(CStruct):
|
|
62
|
+
x: types.int32
|
|
63
|
+
y: types.int32
|
|
64
|
+
|
|
65
|
+
class Rectangle(CStruct):
|
|
66
|
+
top_left: Point
|
|
67
|
+
bottom_right: Point
|
|
68
|
+
color: types.uint32 = 0xFF0000 # Default value
|
|
69
|
+
|
|
70
|
+
# Create instances
|
|
71
|
+
point = Point(x=10, y=20)
|
|
72
|
+
rect = Rectangle(
|
|
73
|
+
top_left=Point(x=0, y=0),
|
|
74
|
+
bottom_right=Point(x=100, y=100)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
print(rect) # Rectangle(top_left=Point(x=0, y=0), bottom_right=Point(x=100, y=100), color=16711680)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Working with Arrays
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from typing import Literal
|
|
84
|
+
|
|
85
|
+
from cbridge import CStruct
|
|
86
|
+
from cbridge import types
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Data(CStruct):
|
|
90
|
+
id: types.int32
|
|
91
|
+
values: types.Array[types.int8, Literal[4]] # Array of 4 int8 values
|
|
92
|
+
name: types.char_ptr
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Create with array data
|
|
96
|
+
data = Data(id=1, values=[1, 2, 3, 4], name=b"test")
|
|
97
|
+
print(data.values) # [1, 2, 3, 4]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### C Function Binding
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from cbridge import cfunc
|
|
104
|
+
from cbridge import types
|
|
105
|
+
|
|
106
|
+
@cfunc("c") # Bind to libc
|
|
107
|
+
def time(t: types.Pointer[types.ctime_t]) -> types.ctime_t: ...
|
|
108
|
+
|
|
109
|
+
# Call the C function
|
|
110
|
+
current_time = time(None)
|
|
111
|
+
print(current_time)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Memory Operations
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import array
|
|
118
|
+
|
|
119
|
+
from cbridge import CStruct
|
|
120
|
+
from cbridge import types
|
|
121
|
+
|
|
122
|
+
class Point(CStruct):
|
|
123
|
+
x: types.int32
|
|
124
|
+
y: types.int32
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# Create from buffer
|
|
128
|
+
buffer = array.array("B", b"\x01\x00\x00\x00\x02\x00\x00\x00")
|
|
129
|
+
point = Point.from_buffer(buffer)
|
|
130
|
+
print(point) # Point(x=1, y=2)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Advanced Usage
|
|
134
|
+
|
|
135
|
+
### Structure Packing
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import ctypes
|
|
139
|
+
|
|
140
|
+
from cbridge import CStruct
|
|
141
|
+
from cbridge import types
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class DefaultPackData(CStruct): # 1-byte alignment
|
|
145
|
+
a: types.char
|
|
146
|
+
b: types.int32
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class Pack1Data(CStruct, pack=1): # 1-byte alignment
|
|
150
|
+
a: types.char
|
|
151
|
+
b: types.int32
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
print(ctypes.sizeof(DefaultPackData)) # 8 bytes
|
|
155
|
+
print(ctypes.sizeof(Pack1Data)) # 5 bytes
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Default Values and Factories
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from typing import Literal
|
|
163
|
+
|
|
164
|
+
from cbridge import CStruct
|
|
165
|
+
from cbridge import field
|
|
166
|
+
from cbridge import types
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class Config(CStruct):
|
|
170
|
+
name: types.char_ptr
|
|
171
|
+
timeout: types.int32 = 30
|
|
172
|
+
flags: types.Array[types.int32, Literal[2]] = field(default_factory=lambda: [0, 1])
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
print(Config(name=b"test")) # Config(name=b'test', timeout=30, flags=[0, 1])
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Pointer Operations
|
|
179
|
+
|
|
180
|
+
> [!WARNING]
|
|
181
|
+
> Python 3.13 and above are currently not supported forward declarations
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from cbridge import CStruct
|
|
185
|
+
from cbridge import types
|
|
186
|
+
from cbridge.types import pointer
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class Node(CStruct):
|
|
190
|
+
data: types.int32
|
|
191
|
+
next: "types.Pointer[Node]"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Create linked list
|
|
195
|
+
node1 = Node(data=1, next=None)
|
|
196
|
+
node2 = Node(data=2, next=pointer(node1))
|
|
197
|
+
node1.next = pointer(node2)
|
|
198
|
+
|
|
199
|
+
node = node1
|
|
200
|
+
data = []
|
|
201
|
+
for _ in range(8):
|
|
202
|
+
data.append(node.data)
|
|
203
|
+
node = node.next[0] # same as *node.next
|
|
204
|
+
assert data == [1, 2, 1, 2, 1, 2, 1, 2]
|
|
205
|
+
```
|
cbridge-0.1.0/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# cbridge
|
|
2
|
+
|
|
3
|
+
[](https://github.com/astral-sh/uv)
|
|
4
|
+
[](https://github.com/astral-sh/ruff)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
[](https://github.com/zen-xu/cbridge/actions/workflows/test.yaml)
|
|
9
|
+
[](https://codecov.io/gh/zen-xu/cbridge)
|
|
10
|
+
|
|
11
|
+
A Python library that provides a dataclass-like interface for creating ctypes structures, making it easier to work with C libraries and data structures in Python.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Dataclass-like syntax**: Define C structures using familiar Python class syntax with type hints
|
|
16
|
+
- **Automatic field management**: Fields are automatically converted to ctypes types based on type annotations
|
|
17
|
+
- **Array support**: Built-in support for fixed-size arrays with proper type checking
|
|
18
|
+
- **Pointer support**: Easy handling of C pointers with type safety
|
|
19
|
+
- **Default values**: Support for default field values and factories
|
|
20
|
+
- **Inheritance**: Full support for class inheritance in C structures
|
|
21
|
+
- **Memory layout control**: Control structure packing and alignment
|
|
22
|
+
- **C function binding**: Decorator for easy binding to C library functions
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install cbridge
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### Creating C Structures
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from cbridge import CStruct
|
|
36
|
+
from cbridge import types
|
|
37
|
+
|
|
38
|
+
class Point(CStruct):
|
|
39
|
+
x: types.int32
|
|
40
|
+
y: types.int32
|
|
41
|
+
|
|
42
|
+
class Rectangle(CStruct):
|
|
43
|
+
top_left: Point
|
|
44
|
+
bottom_right: Point
|
|
45
|
+
color: types.uint32 = 0xFF0000 # Default value
|
|
46
|
+
|
|
47
|
+
# Create instances
|
|
48
|
+
point = Point(x=10, y=20)
|
|
49
|
+
rect = Rectangle(
|
|
50
|
+
top_left=Point(x=0, y=0),
|
|
51
|
+
bottom_right=Point(x=100, y=100)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
print(rect) # Rectangle(top_left=Point(x=0, y=0), bottom_right=Point(x=100, y=100), color=16711680)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Working with Arrays
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from typing import Literal
|
|
61
|
+
|
|
62
|
+
from cbridge import CStruct
|
|
63
|
+
from cbridge import types
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Data(CStruct):
|
|
67
|
+
id: types.int32
|
|
68
|
+
values: types.Array[types.int8, Literal[4]] # Array of 4 int8 values
|
|
69
|
+
name: types.char_ptr
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Create with array data
|
|
73
|
+
data = Data(id=1, values=[1, 2, 3, 4], name=b"test")
|
|
74
|
+
print(data.values) # [1, 2, 3, 4]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### C Function Binding
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from cbridge import cfunc
|
|
81
|
+
from cbridge import types
|
|
82
|
+
|
|
83
|
+
@cfunc("c") # Bind to libc
|
|
84
|
+
def time(t: types.Pointer[types.ctime_t]) -> types.ctime_t: ...
|
|
85
|
+
|
|
86
|
+
# Call the C function
|
|
87
|
+
current_time = time(None)
|
|
88
|
+
print(current_time)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Memory Operations
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import array
|
|
95
|
+
|
|
96
|
+
from cbridge import CStruct
|
|
97
|
+
from cbridge import types
|
|
98
|
+
|
|
99
|
+
class Point(CStruct):
|
|
100
|
+
x: types.int32
|
|
101
|
+
y: types.int32
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# Create from buffer
|
|
105
|
+
buffer = array.array("B", b"\x01\x00\x00\x00\x02\x00\x00\x00")
|
|
106
|
+
point = Point.from_buffer(buffer)
|
|
107
|
+
print(point) # Point(x=1, y=2)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Advanced Usage
|
|
111
|
+
|
|
112
|
+
### Structure Packing
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import ctypes
|
|
116
|
+
|
|
117
|
+
from cbridge import CStruct
|
|
118
|
+
from cbridge import types
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class DefaultPackData(CStruct): # 1-byte alignment
|
|
122
|
+
a: types.char
|
|
123
|
+
b: types.int32
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Pack1Data(CStruct, pack=1): # 1-byte alignment
|
|
127
|
+
a: types.char
|
|
128
|
+
b: types.int32
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
print(ctypes.sizeof(DefaultPackData)) # 8 bytes
|
|
132
|
+
print(ctypes.sizeof(Pack1Data)) # 5 bytes
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Default Values and Factories
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from typing import Literal
|
|
140
|
+
|
|
141
|
+
from cbridge import CStruct
|
|
142
|
+
from cbridge import field
|
|
143
|
+
from cbridge import types
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class Config(CStruct):
|
|
147
|
+
name: types.char_ptr
|
|
148
|
+
timeout: types.int32 = 30
|
|
149
|
+
flags: types.Array[types.int32, Literal[2]] = field(default_factory=lambda: [0, 1])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
print(Config(name=b"test")) # Config(name=b'test', timeout=30, flags=[0, 1])
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Pointer Operations
|
|
156
|
+
|
|
157
|
+
> [!WARNING]
|
|
158
|
+
> Python 3.13 and above are currently not supported forward declarations
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from cbridge import CStruct
|
|
162
|
+
from cbridge import types
|
|
163
|
+
from cbridge.types import pointer
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Node(CStruct):
|
|
167
|
+
data: types.int32
|
|
168
|
+
next: "types.Pointer[Node]"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# Create linked list
|
|
172
|
+
node1 = Node(data=1, next=None)
|
|
173
|
+
node2 = Node(data=2, next=pointer(node1))
|
|
174
|
+
node1.next = pointer(node2)
|
|
175
|
+
|
|
176
|
+
node = node1
|
|
177
|
+
data = []
|
|
178
|
+
for _ in range(8):
|
|
179
|
+
data.append(node.data)
|
|
180
|
+
node = node.next[0] # same as *node.next
|
|
181
|
+
assert data == [1, 2, 1, 2, 1, 2, 1, 2]
|
|
182
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cbridge"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = [{ name = "ZhengYu, Xu", email = "zen-xu@outlook.com" }]
|
|
5
|
+
description = "Creating ctype structure like dataclass"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "MIT"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"License :: OSI Approved :: MIT License",
|
|
11
|
+
"Programming Language :: Python",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
14
|
+
"Programming Language :: Python :: 3.9",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
]
|
|
20
|
+
dependencies = ["typing-extensions; python_version< '3.12'"]
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
nox = ["nox", "nox-uv"]
|
|
24
|
+
test = ["pytest", "pytest-coverage"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
homepage = "https://github.com/zen-xu/cbridge"
|
|
28
|
+
repository = "https://github.com/zen-xu/cbridge.git"
|
|
29
|
+
issues = "https://github.com/zen-xu/cbridge/issues"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["uv_build>=0.8,<0.9"]
|
|
33
|
+
build-backend = "uv_build"
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
extend-exclude = ["docs/*"]
|
|
37
|
+
fix = true
|
|
38
|
+
line-length = 88
|
|
39
|
+
target-version = "py39"
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
extend-select = [
|
|
43
|
+
"B", # flake8-bugbear
|
|
44
|
+
"C4", # flake8-comprehensions
|
|
45
|
+
"ERA", # flake8-eradicate/eradicate
|
|
46
|
+
"I", # isort
|
|
47
|
+
"N", # pep8-naming
|
|
48
|
+
"PIE", # flake8-pie
|
|
49
|
+
"PGH", # pygrep
|
|
50
|
+
"RUF", # ruff checks
|
|
51
|
+
"SIM", # flake8-simplify
|
|
52
|
+
"TC", # flake8-type-checking
|
|
53
|
+
"TID", # flake8-tidy-imports
|
|
54
|
+
"UP", # pyupgrade
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint.isort]
|
|
58
|
+
force-single-line = true
|
|
59
|
+
known-first-party = ["cbridge"]
|
|
60
|
+
lines-after-imports = 2
|
|
61
|
+
lines-between-types = 1
|
|
62
|
+
|
|
63
|
+
[tool.ruff.format]
|
|
64
|
+
docstring-code-format = true
|
|
65
|
+
|
|
66
|
+
[tool.mypy]
|
|
67
|
+
files = "src"
|
|
68
|
+
explicit_package_bases = true
|
|
69
|
+
namespace_packages = true
|
|
70
|
+
show_error_codes = true
|
|
71
|
+
|
|
72
|
+
[tool.coverage.run]
|
|
73
|
+
source = ["src/cbridge"]
|
|
74
|
+
branch = true
|
|
75
|
+
omit = ["src/cbridge/types.py"]
|
|
76
|
+
|
|
77
|
+
[tool.coverage.report]
|
|
78
|
+
exclude_lines = ["pragma: no cover", "if TYPE_CHECKING:"]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import dataclasses as ds
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing import ClassVar
|
|
11
|
+
from typing import TypeVar
|
|
12
|
+
from typing import get_origin
|
|
13
|
+
from typing import get_type_hints
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if sys.version_info >= (3, 12):
|
|
17
|
+
from typing import Self
|
|
18
|
+
from typing import dataclass_transform
|
|
19
|
+
else:
|
|
20
|
+
from typing_extensions import Self
|
|
21
|
+
from typing_extensions import dataclass_transform
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_T = TypeVar("_T")
|
|
25
|
+
|
|
26
|
+
_CStructType: type = type(ctypes.Structure)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass_transform()
|
|
30
|
+
class CStructType(_CStructType):
|
|
31
|
+
def __new__(
|
|
32
|
+
meta_self, # type: ignore[misc]
|
|
33
|
+
name: str,
|
|
34
|
+
bases: tuple[type, ...],
|
|
35
|
+
attrs: dict[str, Any],
|
|
36
|
+
pack: int = 0,
|
|
37
|
+
**extra,
|
|
38
|
+
):
|
|
39
|
+
if sys.version_info >= (3, 13):
|
|
40
|
+
attrs["_fields_"] = []
|
|
41
|
+
attrs["_pack_"] = pack
|
|
42
|
+
cls = super().__new__(meta_self, name, bases, attrs)
|
|
43
|
+
fields_map = {
|
|
44
|
+
f_name: f_type
|
|
45
|
+
for f_name, f_type in get_type_hints(
|
|
46
|
+
cls, localns={**locals(), name: cls}
|
|
47
|
+
).items()
|
|
48
|
+
if get_origin(f_type) is not ClassVar
|
|
49
|
+
}
|
|
50
|
+
fields = list(fields_map.items())
|
|
51
|
+
if fields:
|
|
52
|
+
# update fields
|
|
53
|
+
if sys.version_info >= (3, 13):
|
|
54
|
+
cls._fields_[:] = fields
|
|
55
|
+
else:
|
|
56
|
+
cls._fields_ = fields
|
|
57
|
+
|
|
58
|
+
cls = ds.dataclass(cls)
|
|
59
|
+
|
|
60
|
+
@wraps(cls.__init__)
|
|
61
|
+
def wrapped_init(self, *args, **kwargs):
|
|
62
|
+
args = list(args)
|
|
63
|
+
|
|
64
|
+
for i, arg in enumerate(args):
|
|
65
|
+
field_name = fields[i][0]
|
|
66
|
+
kwargs[field_name] = arg
|
|
67
|
+
|
|
68
|
+
# fill default values
|
|
69
|
+
for field_name, _ in fields:
|
|
70
|
+
if field_name in kwargs:
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if field_option := attrs.get(field_name):
|
|
74
|
+
if isinstance(field_option, ds.Field):
|
|
75
|
+
if field_option.default is not ds.MISSING:
|
|
76
|
+
kwargs[field_name] = field_option.default
|
|
77
|
+
elif field_option.default_factory is not ds.MISSING:
|
|
78
|
+
kwargs[field_name] = field_option.default_factory()
|
|
79
|
+
else:
|
|
80
|
+
kwargs[field_name] = field_option
|
|
81
|
+
|
|
82
|
+
for arg_name, arg_value in kwargs.items():
|
|
83
|
+
field_type = fields_map[arg_name]
|
|
84
|
+
if issubclass(field_type, ctypes.Array):
|
|
85
|
+
kwargs[arg_name] = field_type(*arg_value)
|
|
86
|
+
|
|
87
|
+
return ctypes.Structure.__init__(self, **kwargs)
|
|
88
|
+
|
|
89
|
+
cls.__init__ = wrapped_init
|
|
90
|
+
|
|
91
|
+
return cls
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
field = ds.field
|
|
95
|
+
|
|
96
|
+
if TYPE_CHECKING:
|
|
97
|
+
|
|
98
|
+
@dataclass_transform(field_specifiers=(field,))
|
|
99
|
+
class CStruct(ctypes.Structure):
|
|
100
|
+
def __init_subclass__(cls, pack: int = 0) -> None: ...
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def empty(cls) -> Self:
|
|
104
|
+
"""Create an empty instance of the structure."""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
|
|
109
|
+
class CStruct(ctypes.Structure, metaclass=CStructType):
|
|
110
|
+
@classmethod
|
|
111
|
+
def empty(cls) -> Self:
|
|
112
|
+
return cls()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
|
|
5
|
+
from ctypes.util import find_library
|
|
6
|
+
from typing import Callable
|
|
7
|
+
from typing import TypeVar
|
|
8
|
+
from typing import get_type_hints
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_F = TypeVar("_F", bound=Callable)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def cfunc(clib: str | ctypes.CDLL) -> Callable[[_F], _F]:
|
|
15
|
+
"""
|
|
16
|
+
Decorator to bind a Python function signature to a C function from a shared library.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
clib (str | ctypes.CDLL): The name of the C library (as a string) or a ctypes.CDLL instance.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Callable[[_F], _F]: A decorator that replaces the Python function with the corresponding C function,
|
|
23
|
+
setting its argument and return types based on the Python type hints.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
>>> from cbridge import cfunc
|
|
27
|
+
>>> from cbridge import types
|
|
28
|
+
>>> from cbridge.types import pointer
|
|
29
|
+
>>> @cfunc("c")
|
|
30
|
+
... def time(t: types.Pointer[types.ctime_t]) -> types.ctime_t: ...
|
|
31
|
+
>>> print(time(None))
|
|
32
|
+
>>> 1727433600
|
|
33
|
+
"""
|
|
34
|
+
clib = ctypes.CDLL(find_library(clib)) if isinstance(clib, str) else clib
|
|
35
|
+
|
|
36
|
+
def wrapper(func: _F) -> _F:
|
|
37
|
+
hints = get_type_hints(func)
|
|
38
|
+
restype = hints.pop("return", None)
|
|
39
|
+
argtypes = list(hints.values())
|
|
40
|
+
cfunc = getattr(clib, func.__name__)
|
|
41
|
+
cfunc.restype = restype
|
|
42
|
+
cfunc.argtypes = tuple(argtypes)
|
|
43
|
+
return cfunc
|
|
44
|
+
|
|
45
|
+
return wrapper
|
|
File without changes
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
from typing import Generic
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
from typing import overload
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_T = TypeVar("_T")
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from _ctypes import _CData as CData
|
|
14
|
+
from typing import Any
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
bool = Union[bool, CData]
|
|
18
|
+
byte = Union[bytes, CData]
|
|
19
|
+
char = Union[bytes, CData]
|
|
20
|
+
double = Union[float, CData]
|
|
21
|
+
float = Union[float, CData]
|
|
22
|
+
ubyte = Union[bytes, CData]
|
|
23
|
+
int = Union[int, CData]
|
|
24
|
+
int8 = Union[int, CData]
|
|
25
|
+
int16 = Union[int, CData]
|
|
26
|
+
int32 = Union[int, CData]
|
|
27
|
+
int64 = Union[int, CData]
|
|
28
|
+
long = Union[int, CData]
|
|
29
|
+
longdouble = Union[float, CData]
|
|
30
|
+
longlong = Union[float, CData]
|
|
31
|
+
short = Union[int, CData]
|
|
32
|
+
size_t = Union[int, CData]
|
|
33
|
+
ssize_t = Union[int, CData]
|
|
34
|
+
uint = Union[int, CData]
|
|
35
|
+
uint8 = Union[int, CData]
|
|
36
|
+
uint16 = Union[int, CData]
|
|
37
|
+
uint32 = Union[int, CData]
|
|
38
|
+
uint64 = Union[int, CData]
|
|
39
|
+
ulong = Union[int, CData]
|
|
40
|
+
ulonglong = Union[int, CData]
|
|
41
|
+
ushort = Union[int, CData]
|
|
42
|
+
wchar = Union[str, CData]
|
|
43
|
+
void_ptr = Union[ctypes.c_void_p, Any]
|
|
44
|
+
|
|
45
|
+
_Len = TypeVar("_Len")
|
|
46
|
+
|
|
47
|
+
class _Array(Generic[_T, _Len], ctypes.Array[_T]): ... # type: ignore[type-var]
|
|
48
|
+
|
|
49
|
+
Array = Union[_Array[_T, _Len], Sequence[_T]]
|
|
50
|
+
|
|
51
|
+
class _Pointer(ctypes._Pointer[_T]): # type: ignore[type-var]
|
|
52
|
+
@overload
|
|
53
|
+
def __getitem__(self, index: int) -> _T: ...
|
|
54
|
+
@overload
|
|
55
|
+
def __getitem__(self, index: slice) -> list[_T]: ...
|
|
56
|
+
def __getitem__(self, index: int | slice) -> _T | list[_T]: ...
|
|
57
|
+
|
|
58
|
+
Pointer = Union[_Pointer[_T], None]
|
|
59
|
+
|
|
60
|
+
char_ptr = Pointer[char] | bytes
|
|
61
|
+
wchar_ptr = Pointer[wchar] | str
|
|
62
|
+
|
|
63
|
+
def pointer(obj: _T) -> Pointer[_T]: ...
|
|
64
|
+
else:
|
|
65
|
+
CData = object
|
|
66
|
+
byte = ctypes.c_byte
|
|
67
|
+
bool = ctypes.c_bool
|
|
68
|
+
char = ctypes.c_char
|
|
69
|
+
double = ctypes.c_double
|
|
70
|
+
float = ctypes.c_float
|
|
71
|
+
ubyte = ctypes.c_ubyte
|
|
72
|
+
int = ctypes.c_int
|
|
73
|
+
int8 = ctypes.c_int8
|
|
74
|
+
int16 = ctypes.c_int16
|
|
75
|
+
int32 = ctypes.c_int32
|
|
76
|
+
int64 = ctypes.c_int64
|
|
77
|
+
long = ctypes.c_long
|
|
78
|
+
longdouble = ctypes.c_longdouble
|
|
79
|
+
longlong = ctypes.c_longlong
|
|
80
|
+
short = ctypes.c_short
|
|
81
|
+
size_t = ctypes.c_size_t
|
|
82
|
+
ssize_t = ctypes.c_ssize_t
|
|
83
|
+
uint = ctypes.c_uint
|
|
84
|
+
uint8 = ctypes.c_uint8
|
|
85
|
+
uint16 = ctypes.c_uint16
|
|
86
|
+
uint32 = ctypes.c_uint32
|
|
87
|
+
uint64 = ctypes.c_uint64
|
|
88
|
+
ulong = ctypes.c_ulong
|
|
89
|
+
ulonglong = ctypes.c_ulonglong
|
|
90
|
+
ushort = ctypes.c_ushort
|
|
91
|
+
wchar = ctypes.c_wchar
|
|
92
|
+
void_ptr = ctypes.c_void_p
|
|
93
|
+
char_ptr = ctypes.c_char_p
|
|
94
|
+
wchar_ptr = ctypes.c_wchar_p
|
|
95
|
+
|
|
96
|
+
import builtins
|
|
97
|
+
|
|
98
|
+
from typing import get_args
|
|
99
|
+
|
|
100
|
+
class Array:
|
|
101
|
+
def __class_getitem__(cls, types: tuple[type[CData], int]):
|
|
102
|
+
ctype, length = types
|
|
103
|
+
if not isinstance(length, builtins.int):
|
|
104
|
+
length = get_args(length)[0]
|
|
105
|
+
|
|
106
|
+
return type(f"Array_{ctype.__name__}_{length}", (ctype * length, cls), {})
|
|
107
|
+
|
|
108
|
+
def __repr__(self):
|
|
109
|
+
return str(list(self))
|
|
110
|
+
|
|
111
|
+
def __eq__(self, value: object, /) -> bool:
|
|
112
|
+
if not isinstance(value, Sequence):
|
|
113
|
+
return False
|
|
114
|
+
return list(self) == list(value)
|
|
115
|
+
|
|
116
|
+
class Pointer(ctypes._Pointer):
|
|
117
|
+
def __class_getitem__(cls, type):
|
|
118
|
+
cls = ctypes.POINTER(type)
|
|
119
|
+
|
|
120
|
+
def pointer_repr(self):
|
|
121
|
+
try:
|
|
122
|
+
_ = self.contents
|
|
123
|
+
except ValueError:
|
|
124
|
+
return "nullptr"
|
|
125
|
+
else:
|
|
126
|
+
return f"*{type.__name__}"
|
|
127
|
+
|
|
128
|
+
cls.__repr__ = pointer_repr
|
|
129
|
+
|
|
130
|
+
return cls
|
|
131
|
+
|
|
132
|
+
pointer = ctypes.pointer
|
|
133
|
+
|
|
134
|
+
ctime_t = ulong
|