Repository navigation
Expand file tree
/
Copy path_utils.py
More file actions
171 lines (135 loc) · 4.22 KB
/
Copy path_utils.py
File metadata and controls
171 lines (135 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import ctypes
from collections.abc import Callable
from types import FunctionType, MethodType
from typing import Any, Dict, Type, Union
from _pointers import force_set_attr as _force_set_attr
from .exceptions import InvalidSizeError
__all__ = (
"attempt_decode",
"move_to_mem",
"map_type",
"get_mapped",
"is_mappable",
"get_py",
"make_py",
)
_C_TYPES: Dict[Type[Any], Type["ctypes._CData"]] = {
bytes: ctypes.c_char_p,
str: ctypes.c_wchar_p,
int: ctypes.c_int,
float: ctypes.c_float,
bool: ctypes.c_bool,
object: ctypes.py_object,
type: ctypes.py_object
}
_PY_TYPES: Dict[Type["ctypes._CData"], type] = {
ctypes.c_bool: bool,
ctypes.c_char: bytes,
ctypes.c_wchar: str,
ctypes.c_ubyte: int,
ctypes.c_short: int,
ctypes.c_int: int,
ctypes.c_uint: int,
ctypes.c_long: int,
ctypes.c_ulong: int,
ctypes.c_longlong: int,
ctypes.c_ulonglong: int,
ctypes.c_size_t: int,
ctypes.c_ssize_t: int,
ctypes.c_float: float,
ctypes.c_double: float,
ctypes.c_longdouble: float,
ctypes.c_char_p: bytes,
ctypes.c_wchar_p: str,
ctypes.c_void_p: int,
ctypes.py_object: Any, # type: ignore
}
def move_to_mem(
ptr: "ctypes._PointerLike",
stream: bytes,
*,
unsafe: bool = False,
target: str = "memory allocation",
):
"""Move data to a C pointer."""
slen = len(stream)
plen = len(ptr.contents) # type: ignore
if (slen > plen) and (not unsafe):
raise InvalidSizeError(
f"object is of size {slen}, while {target} is {plen}",
)
ctypes.memmove(ptr, stream, slen)
def attempt_decode(data: bytes) -> Union[str, bytes]:
"""Attempt to decode a string of bytes."""
try:
return data.decode()
except UnicodeDecodeError:
return data
def map_type(data: Any) -> "ctypes._CData":
"""Map the specified data to a C type."""
typ = get_mapped(type(data))
try:
return typ(data) # type: ignore
except TypeError:
return ctypes.py_object(data)
def get_mapped(typ: Any) -> "Type[ctypes._CData]":
"""Get the C mapped value of the given type."""
from .c_pointer import VoidPointer
if getattr(typ, "__origin__", None) is Callable:
args = list(typ.__args__)
res = args.pop(-1)
return ctypes.CFUNCTYPE(get_mapped(res), *map(get_mapped, args))
if type(typ) in {FunctionType, MethodType}:
hints = typ.__annotations__.copy()
try:
res = hints.pop("return")
except KeyError as e:
raise TypeError(
"return type annotation is required to convert to a C function" # noqa
) from e
args = hints.values()
return ctypes.CFUNCTYPE(
get_mapped(res) if res else None,
*map(get_mapped, args),
)
# VoidPointer needs to be passed here to stop circular imports
return {**_C_TYPES, VoidPointer: ctypes.c_void_p}.get( # type: ignore
typ,
) or ctypes.py_object
def is_mappable(typ: Any) -> bool:
"""Whether the specified type is mappable to C."""
try:
get_mapped(typ)
return True
except ValueError:
return False
def get_py(
data: Type["ctypes._CData"],
) -> Type[Any]:
"""Map the specified C type to a Python type."""
from .base_pointers import BaseCPointer
if data.__name__.startswith("LP_"):
return BaseCPointer
try:
return _PY_TYPES[data]
except KeyError as e:
raise ValueError(
f"{data} is not a valid ctypes type",
) from e
def make_py(data: "ctypes._CData"):
"""Convert the target C value to a Python object."""
typ = get_py(type(data))
res = typ(data)
if typ is bytes:
res = attempt_decode(res)
return res
def force_set_attr(typ: Type[Any], key: str, value: Any) -> None:
"""Force setting an attribute on the target type."""
if not isinstance(typ, type):
raise ValueError(
f"{typ} does not derive from type (did you pass an instance and not a class)?", # noqa
)
_force_set_attr(typ, key, value)
def deref(address: int) -> Any:
"""Get the value at the target address."""
return ctypes.cast(address, ctypes.py_object).value