pydtype-cpp 0.1.0__py3-none-any.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.
pydtype/desc.hpp
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// C++26 reflection to numpy dtypes
|
|
3
|
+
//
|
|
4
|
+
// For this code to compile with gcc16 include the following flags: -std=c++26 -freflection
|
|
5
|
+
//
|
|
6
|
+
// Author: Paul Scruby
|
|
7
|
+
// Copyright: Paul Scruby
|
|
8
|
+
// License: MIT
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
#pragma once
|
|
12
|
+
|
|
13
|
+
#include <bit>
|
|
14
|
+
#include <chrono>
|
|
15
|
+
#include <concepts>
|
|
16
|
+
#include <meta>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <string_view>
|
|
19
|
+
#include <type_traits>
|
|
20
|
+
#include <cmath>
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
namespace pydtype {
|
|
24
|
+
|
|
25
|
+
enum class Endian {
|
|
26
|
+
NONE,
|
|
27
|
+
LITTLE,
|
|
28
|
+
BIG,
|
|
29
|
+
NATIVE,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
namespace detail {
|
|
33
|
+
|
|
34
|
+
template <std::size_t N>
|
|
35
|
+
struct FixedString {
|
|
36
|
+
char data[N]{};
|
|
37
|
+
|
|
38
|
+
constexpr operator std::string_view() const {
|
|
39
|
+
return std::string_view(data, N);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
template <std::size_t N> consteval auto const_to_string() {
|
|
44
|
+
if constexpr (N == 0) {
|
|
45
|
+
return FixedString<1>{{'0'}};
|
|
46
|
+
} else {
|
|
47
|
+
constexpr auto num_digits = [](std::size_t val) {
|
|
48
|
+
if (val == 0) {
|
|
49
|
+
return std::size_t{1};
|
|
50
|
+
}
|
|
51
|
+
return static_cast<std::size_t>(std::log10(val)) + 1;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
constexpr std::size_t len = num_digits(N);
|
|
55
|
+
FixedString<len> buffer;
|
|
56
|
+
std::size_t tmp = N;
|
|
57
|
+
|
|
58
|
+
for (std::size_t i = 0; i < len; i++) {
|
|
59
|
+
buffer.data[len - 1 - i] = static_cast<char>('0' + (tmp % 10));
|
|
60
|
+
tmp /= 10;
|
|
61
|
+
}
|
|
62
|
+
return buffer;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
template <typename T> consteval std::string_view array_dims() {
|
|
67
|
+
if constexpr (std::is_array_v<T>) {
|
|
68
|
+
static constexpr std::size_t ext = std::extent_v<T>;
|
|
69
|
+
static constexpr auto num_str = const_to_string<ext>();
|
|
70
|
+
static constexpr auto next_view = array_dims<std::remove_extent_t<T>>();
|
|
71
|
+
|
|
72
|
+
constexpr std::size_t total_size = 2 + sizeof(num_str.data) + next_view.size();
|
|
73
|
+
|
|
74
|
+
static constexpr auto assembled = []() {
|
|
75
|
+
FixedString<total_size> buffer;
|
|
76
|
+
buffer.data[0] = ',';
|
|
77
|
+
buffer.data[1] = ' ';
|
|
78
|
+
std::size_t idx = 2;
|
|
79
|
+
|
|
80
|
+
for (std::size_t i = 0; i < sizeof(num_str.data); i++) {
|
|
81
|
+
buffer.data[idx++] = num_str.data[i];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (std::size_t i = 0; i < next_view.size(); i++) {
|
|
85
|
+
buffer.data[idx++] = next_view[i];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return buffer;
|
|
89
|
+
}();
|
|
90
|
+
|
|
91
|
+
return std::string_view(assembled.data, sizeof(assembled.data));
|
|
92
|
+
} else {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
template<typename T> struct always_false : std::false_type {};
|
|
98
|
+
template<typename T> struct is_chrono_time_point : std::false_type {};
|
|
99
|
+
template<typename C, typename D> struct is_chrono_time_point<std::chrono::time_point<C, D>> : std::true_type {};
|
|
100
|
+
template<typename T> struct is_chrono_duration : std::false_type {};
|
|
101
|
+
template<typename R, typename P> struct is_chrono_duration<std::chrono::duration<R, P>> : std::true_type {};
|
|
102
|
+
template<typename T> struct underlying_type { using type = T; };
|
|
103
|
+
template<typename T> requires std::is_enum_v<T> struct underlying_type<T> { using type = std::underlying_type_t<T>; };
|
|
104
|
+
template<typename T> using underlying_type_t = typename underlying_type<T>::type;
|
|
105
|
+
|
|
106
|
+
template<Endian E> consteval std::string_view endian_prefix();
|
|
107
|
+
template<> consteval std::string_view endian_prefix<Endian::NONE>() { return ""; }
|
|
108
|
+
template<> consteval std::string_view endian_prefix<Endian::LITTLE>() { return "<"; }
|
|
109
|
+
template<> consteval std::string_view endian_prefix<Endian::BIG>() { return ">"; }
|
|
110
|
+
template<> consteval std::string_view endian_prefix<Endian::NATIVE>() { return std::endian::native == std::endian::little ? "<" : ">"; }
|
|
111
|
+
|
|
112
|
+
template<typename T> consteval std::string_view np_scalar_type() {
|
|
113
|
+
if constexpr (is_chrono_time_point<T>::value) {
|
|
114
|
+
using duration_t = typename T::duration;
|
|
115
|
+
if constexpr (std::same_as<duration_t, std::chrono::nanoseconds>) {
|
|
116
|
+
return "M8[ns]";
|
|
117
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::microseconds>) {
|
|
118
|
+
return "M8[us]";
|
|
119
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::milliseconds>) {
|
|
120
|
+
return "M8[ms]";
|
|
121
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::seconds>) {
|
|
122
|
+
return "M8[s]";
|
|
123
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::minutes>) {
|
|
124
|
+
return "M8[m]";
|
|
125
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::hours>) {
|
|
126
|
+
return "M8[h]";
|
|
127
|
+
} else {
|
|
128
|
+
static_assert(always_false<T>::value, "Unsupported std::chrono::time_point resolution");
|
|
129
|
+
}
|
|
130
|
+
} else if constexpr (is_chrono_duration<T>::value) {
|
|
131
|
+
if constexpr (std::same_as<T, std::chrono::nanoseconds>) {
|
|
132
|
+
return "m8[ns]";
|
|
133
|
+
} else if constexpr (std::same_as<T, std::chrono::microseconds>) {
|
|
134
|
+
return "m8[us]";
|
|
135
|
+
} else if constexpr (std::same_as<T, std::chrono::milliseconds>) {
|
|
136
|
+
return "m8[ms]";
|
|
137
|
+
} else if constexpr (std::same_as<T, std::chrono::seconds>) {
|
|
138
|
+
return "m8[s]";
|
|
139
|
+
} else if constexpr (std::same_as<T, std::chrono::minutes>) {
|
|
140
|
+
return "m8[m]";
|
|
141
|
+
} else if constexpr (std::same_as<T, std::chrono::hours>) {
|
|
142
|
+
return "m8[h]";
|
|
143
|
+
} else {
|
|
144
|
+
static_assert(always_false<T>::value, "Unsupported std::chrono::duration resolution");
|
|
145
|
+
}
|
|
146
|
+
} else if constexpr (std::same_as<T, bool>) {
|
|
147
|
+
return "b1";
|
|
148
|
+
} else if constexpr (std::same_as<T, char>) {
|
|
149
|
+
return "S1";
|
|
150
|
+
} else if constexpr (std::same_as<T, signed char> or std::same_as<T, int8_t>) {
|
|
151
|
+
return "i1";
|
|
152
|
+
} else if constexpr (std::same_as<T, unsigned char> or std::same_as<T, uint8_t> or std::same_as<T, std::byte>) {
|
|
153
|
+
return "u1";
|
|
154
|
+
} else if constexpr (std::same_as<T, short> or std::same_as<T, int16_t>) {
|
|
155
|
+
return "i2";
|
|
156
|
+
} else if constexpr (std::same_as<T, unsigned short> or std::same_as<T, uint16_t>) {
|
|
157
|
+
return "u2";
|
|
158
|
+
} else if constexpr (std::same_as<T, char16_t>) {
|
|
159
|
+
return "U2";
|
|
160
|
+
} else if constexpr (std::same_as<T, int32_t>) {
|
|
161
|
+
return "i4";
|
|
162
|
+
} else if constexpr (std::same_as<T, uint32_t>) {
|
|
163
|
+
return "u4";
|
|
164
|
+
} else if constexpr (std::same_as<T, char32_t>) {
|
|
165
|
+
return "U4";
|
|
166
|
+
} else if constexpr (std::same_as<T, long long> or std::same_as<T, int64_t>) {
|
|
167
|
+
return "i8";
|
|
168
|
+
} else if constexpr (std::same_as<T, unsigned long long> or std::same_as<T, uint64_t>) {
|
|
169
|
+
return "u8";
|
|
170
|
+
} else if constexpr (std::same_as<T, float>) {
|
|
171
|
+
return "f4";
|
|
172
|
+
} else if constexpr (std::same_as<T, double>) {
|
|
173
|
+
return "f8";
|
|
174
|
+
} else if constexpr (std::same_as<T, long double>) {
|
|
175
|
+
if constexpr (sizeof(long double) == 8) {
|
|
176
|
+
return "f8";
|
|
177
|
+
} else if constexpr (sizeof(long double) == 12) {
|
|
178
|
+
return "f12";
|
|
179
|
+
} else if constexpr (sizeof(long double) == 16) {
|
|
180
|
+
return "f16";
|
|
181
|
+
} else {
|
|
182
|
+
return "g";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
template<typename T> consteval bool explicit_layout() {
|
|
189
|
+
using CleanT = std::remove_cvref_t<T>;
|
|
190
|
+
using UncvR = typename underlying_type<CleanT>::type;
|
|
191
|
+
|
|
192
|
+
if constexpr (std::is_union_v<UncvR>) {
|
|
193
|
+
return true;
|
|
194
|
+
} else if constexpr (not std::is_class_v<UncvR>) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
constexpr auto ctx = std::meta::access_context::unchecked();
|
|
199
|
+
|
|
200
|
+
if constexpr (not std::meta::bases_of(^^UncvR, ctx).empty()) {
|
|
201
|
+
return true;
|
|
202
|
+
} else {
|
|
203
|
+
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^UncvR, ctx));
|
|
204
|
+
std::size_t expected_offset = 0;
|
|
205
|
+
bool is_explicit = false;
|
|
206
|
+
|
|
207
|
+
template for (constexpr std::meta::info member : members) {
|
|
208
|
+
using FieldType = typename[:std::meta::type_of(member):];
|
|
209
|
+
constexpr std::string_view field_name = std::meta::identifier_of(member);
|
|
210
|
+
|
|
211
|
+
if constexpr (std::is_union_v<FieldType> and field_name.empty()) {
|
|
212
|
+
is_explicit = true;
|
|
213
|
+
} else {
|
|
214
|
+
constexpr std::size_t actual_offset = std::is_union_v<UncvR> ? 0 : std::meta::offset_of(member).bytes;
|
|
215
|
+
|
|
216
|
+
if (actual_offset != expected_offset) {
|
|
217
|
+
is_explicit = true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
expected_offset = actual_offset + sizeof(FieldType);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (sizeof(UncvR) != expected_offset) {
|
|
225
|
+
is_explicit = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return is_explicit;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
} // namespace detail
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
236
|
+
// This function generates a numpy dtype description for a C structure using reflection. It supports nested types,
|
|
237
|
+
// multi-dimensional arrays, enums, named and anonymous unions, and chrono to datetime64 and timedelta64 conversion.
|
|
238
|
+
// Where the numpy default alignment differs from C it will update the dtype to match.
|
|
239
|
+
//
|
|
240
|
+
template <typename T, Endian E = Endian::NONE> std::string desc() {
|
|
241
|
+
using RawT = std::remove_cvref_t<T>;
|
|
242
|
+
using UncvR = typename detail::underlying_type<RawT>::type;
|
|
243
|
+
|
|
244
|
+
if constexpr (std::is_array_v<UncvR>) {
|
|
245
|
+
using ElementType = std::remove_all_extents_t<UncvR>;
|
|
246
|
+
|
|
247
|
+
if constexpr (std::same_as<ElementType, char>) {
|
|
248
|
+
return "'S" + std::to_string(sizeof(UncvR)) + "'";
|
|
249
|
+
} else if constexpr (std::same_as<ElementType, char16_t>) {
|
|
250
|
+
return "'" + std::string(detail::endian_prefix<E>()) + "U" + std::to_string(sizeof(UncvR) / 2) + "'";
|
|
251
|
+
} else if constexpr (std::same_as<ElementType, char32_t>) {
|
|
252
|
+
return "'" + std::string(detail::endian_prefix<E>()) + "U" + std::to_string(sizeof(UncvR) / 4) + "'";
|
|
253
|
+
} else {
|
|
254
|
+
return "(" + desc<ElementType, E>() + std::string(detail::array_dims<UncvR>()) + ")";
|
|
255
|
+
}
|
|
256
|
+
} else if constexpr (constexpr std::string_view scalar = detail::np_scalar_type<UncvR>(); not scalar.empty()) {
|
|
257
|
+
if (scalar == "b1" or scalar == "S1" or scalar == "i1" or scalar == "u1") {
|
|
258
|
+
return "'" + std::string(scalar) + "'";
|
|
259
|
+
} else {
|
|
260
|
+
return "'" + std::string(detail::endian_prefix<E>()) + std::string(scalar) + "'";
|
|
261
|
+
}
|
|
262
|
+
} else if constexpr (std::is_union_v<UncvR> or std::is_class_v<UncvR>) {
|
|
263
|
+
constexpr auto ctx = std::meta::access_context::unchecked();
|
|
264
|
+
constexpr bool has_bases = not std::meta::bases_of(^^UncvR, ctx).empty();
|
|
265
|
+
|
|
266
|
+
if constexpr (not has_bases and not detail::explicit_layout<UncvR>()) { // default numpy layout
|
|
267
|
+
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^UncvR, ctx));
|
|
268
|
+
std::string list = "[";
|
|
269
|
+
bool is_first = true;
|
|
270
|
+
|
|
271
|
+
template for (constexpr std::meta::info member : members) {
|
|
272
|
+
constexpr std::string_view field_name = std::meta::identifier_of(member);
|
|
273
|
+
using FieldType = typename[:std::meta::type_of(member):];
|
|
274
|
+
|
|
275
|
+
if (not is_first) {
|
|
276
|
+
list += ", ";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
is_first = false;
|
|
280
|
+
list += "('" + std::string(field_name) + "', " + desc<FieldType, E>() + ")";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
list += "]";
|
|
284
|
+
return list;
|
|
285
|
+
} else { // explicit dict layout due to base classes, padding, or unions
|
|
286
|
+
std::string names = "'names': [";
|
|
287
|
+
std::string formats = "'formats': [";
|
|
288
|
+
std::string offsets = "'offsets': [";
|
|
289
|
+
bool is_first = true;
|
|
290
|
+
|
|
291
|
+
if constexpr (has_bases) {
|
|
292
|
+
static constexpr auto bases = std::define_static_array(std::meta::bases_of(^^UncvR, ctx));
|
|
293
|
+
|
|
294
|
+
template for (constexpr std::meta::info base : bases) {
|
|
295
|
+
using BaseType = typename[:std::meta::type_of(base):];
|
|
296
|
+
constexpr std::size_t base_offset = std::meta::offset_of(base).bytes;
|
|
297
|
+
static constexpr auto base_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^BaseType, ctx));
|
|
298
|
+
|
|
299
|
+
template for (constexpr std::meta::info bm : base_members) {
|
|
300
|
+
if (not is_first) {
|
|
301
|
+
names += ", ";
|
|
302
|
+
formats += ", ";
|
|
303
|
+
offsets += ", ";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
is_first = false;
|
|
307
|
+
names += "'" + std::string(std::meta::identifier_of(bm)) + "'";
|
|
308
|
+
formats += desc<typename[:std::meta::type_of(bm):], E>();
|
|
309
|
+
offsets += std::to_string(base_offset + (std::is_union_v<BaseType> ? 0 : std::meta::offset_of(bm).bytes));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static constexpr auto local_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^UncvR, ctx));
|
|
315
|
+
|
|
316
|
+
template for (constexpr std::meta::info member : local_members) {
|
|
317
|
+
constexpr std::string_view field_name = std::meta::identifier_of(member);
|
|
318
|
+
using FieldType = typename[:std::meta::type_of(member):];
|
|
319
|
+
constexpr std::size_t offset = std::is_union_v<UncvR> ? 0 : std::meta::offset_of(member).bytes;
|
|
320
|
+
|
|
321
|
+
if constexpr (std::is_union_v<FieldType> and field_name.empty()) { // flatten anonymous unions
|
|
322
|
+
static constexpr auto union_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^FieldType, ctx));
|
|
323
|
+
|
|
324
|
+
template for (constexpr std::meta::info u_member : union_members) {
|
|
325
|
+
constexpr std::string_view u_name = std::meta::identifier_of(u_member);
|
|
326
|
+
using UFieldType = typename[:std::meta::type_of(u_member):];
|
|
327
|
+
|
|
328
|
+
if (not is_first) {
|
|
329
|
+
names += ", ";
|
|
330
|
+
formats += ", ";
|
|
331
|
+
offsets += ", ";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
is_first = false;
|
|
335
|
+
names += "'" + std::string(u_name) + "'";
|
|
336
|
+
formats += desc<UFieldType, E>();
|
|
337
|
+
offsets += std::to_string(offset);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
if (not is_first) {
|
|
341
|
+
names += ", ";
|
|
342
|
+
formats += ", ";
|
|
343
|
+
offsets += ", ";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
is_first = false;
|
|
347
|
+
names += "'" + std::string(field_name) + "'";
|
|
348
|
+
formats += desc<FieldType, E>();
|
|
349
|
+
offsets += std::to_string(offset);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
names += "]";
|
|
354
|
+
formats += "]";
|
|
355
|
+
offsets += "]";
|
|
356
|
+
return "{" + names + ", " + formats + ", " + offsets + ", 'itemsize': " + std::to_string(sizeof(UncvR)) + "}";
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
static_assert(detail::always_false<UncvR>::value, "Unsupported member type found during dtype conversion");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
} // namespace pydtype
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
2
|
+
// C++26 reflection to numpy dtypes
|
|
3
|
+
//
|
|
4
|
+
// For this code to compile with gcc16 include the following flags: -std=c++26 -freflection
|
|
5
|
+
//
|
|
6
|
+
// Author: Paul Scruby
|
|
7
|
+
// Copyright: Paul Scruby
|
|
8
|
+
// License: MIT
|
|
9
|
+
//
|
|
10
|
+
|
|
11
|
+
#pragma once
|
|
12
|
+
|
|
13
|
+
#include <bit>
|
|
14
|
+
#include <chrono>
|
|
15
|
+
#include <concepts>
|
|
16
|
+
#include <meta>
|
|
17
|
+
#include <string>
|
|
18
|
+
#include <string_view>
|
|
19
|
+
#include <type_traits>
|
|
20
|
+
#include <cmath>
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
namespace pydtype {
|
|
24
|
+
|
|
25
|
+
enum class Endian {
|
|
26
|
+
NONE,
|
|
27
|
+
LITTLE,
|
|
28
|
+
BIG,
|
|
29
|
+
NATIVE,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
namespace detail {
|
|
33
|
+
|
|
34
|
+
template <std::size_t N>
|
|
35
|
+
struct FixedString {
|
|
36
|
+
char data[N]{};
|
|
37
|
+
|
|
38
|
+
constexpr operator std::string_view() const {
|
|
39
|
+
return std::string_view(data, N);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
template <std::size_t N> consteval auto const_to_string() {
|
|
44
|
+
if constexpr (N == 0) {
|
|
45
|
+
return FixedString<1>{{'0'}};
|
|
46
|
+
} else {
|
|
47
|
+
constexpr auto num_digits = [](std::size_t val) {
|
|
48
|
+
if (val == 0) {
|
|
49
|
+
return std::size_t{1};
|
|
50
|
+
}
|
|
51
|
+
return static_cast<std::size_t>(std::log10(val)) + 1;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
constexpr std::size_t len = num_digits(N);
|
|
55
|
+
FixedString<len> buffer;
|
|
56
|
+
std::size_t tmp = N;
|
|
57
|
+
|
|
58
|
+
for (std::size_t i = 0; i < len; i++) {
|
|
59
|
+
buffer.data[len - 1 - i] = static_cast<char>('0' + (tmp % 10));
|
|
60
|
+
tmp /= 10;
|
|
61
|
+
}
|
|
62
|
+
return buffer;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
template <typename T> consteval std::string_view array_dims() {
|
|
67
|
+
if constexpr (std::is_array_v<T>) {
|
|
68
|
+
static constexpr std::size_t ext = std::extent_v<T>;
|
|
69
|
+
static constexpr auto num_str = const_to_string<ext>();
|
|
70
|
+
static constexpr auto next_view = array_dims<std::remove_extent_t<T>>();
|
|
71
|
+
|
|
72
|
+
constexpr std::size_t total_size = 2 + sizeof(num_str.data) + next_view.size();
|
|
73
|
+
|
|
74
|
+
static constexpr auto assembled = []() {
|
|
75
|
+
FixedString<total_size> buffer;
|
|
76
|
+
buffer.data[0] = ',';
|
|
77
|
+
buffer.data[1] = ' ';
|
|
78
|
+
std::size_t idx = 2;
|
|
79
|
+
|
|
80
|
+
for (std::size_t i = 0; i < sizeof(num_str.data); i++) {
|
|
81
|
+
buffer.data[idx++] = num_str.data[i];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (std::size_t i = 0; i < next_view.size(); i++) {
|
|
85
|
+
buffer.data[idx++] = next_view[i];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return buffer;
|
|
89
|
+
}();
|
|
90
|
+
|
|
91
|
+
return std::string_view(assembled.data, sizeof(assembled.data));
|
|
92
|
+
} else {
|
|
93
|
+
return "";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
template<typename T> struct always_false : std::false_type {};
|
|
98
|
+
template<typename T> struct is_chrono_time_point : std::false_type {};
|
|
99
|
+
template<typename C, typename D> struct is_chrono_time_point<std::chrono::time_point<C, D>> : std::true_type {};
|
|
100
|
+
template<typename T> struct is_chrono_duration : std::false_type {};
|
|
101
|
+
template<typename R, typename P> struct is_chrono_duration<std::chrono::duration<R, P>> : std::true_type {};
|
|
102
|
+
template<typename T> struct underlying_type { using type = T; };
|
|
103
|
+
template<typename T> requires std::is_enum_v<T> struct underlying_type<T> { using type = std::underlying_type_t<T>; };
|
|
104
|
+
template<typename T> using underlying_type_t = typename underlying_type<T>::type;
|
|
105
|
+
|
|
106
|
+
template<Endian E> consteval std::string_view endian_prefix();
|
|
107
|
+
template<> consteval std::string_view endian_prefix<Endian::NONE>() { return ""; }
|
|
108
|
+
template<> consteval std::string_view endian_prefix<Endian::LITTLE>() { return "<"; }
|
|
109
|
+
template<> consteval std::string_view endian_prefix<Endian::BIG>() { return ">"; }
|
|
110
|
+
template<> consteval std::string_view endian_prefix<Endian::NATIVE>() { return std::endian::native == std::endian::little ? "<" : ">"; }
|
|
111
|
+
|
|
112
|
+
template<typename T> consteval std::string_view np_scalar_type() {
|
|
113
|
+
if constexpr (is_chrono_time_point<T>::value) {
|
|
114
|
+
using duration_t = typename T::duration;
|
|
115
|
+
if constexpr (std::same_as<duration_t, std::chrono::nanoseconds>) {
|
|
116
|
+
return "M8[ns]";
|
|
117
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::microseconds>) {
|
|
118
|
+
return "M8[us]";
|
|
119
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::milliseconds>) {
|
|
120
|
+
return "M8[ms]";
|
|
121
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::seconds>) {
|
|
122
|
+
return "M8[s]";
|
|
123
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::minutes>) {
|
|
124
|
+
return "M8[m]";
|
|
125
|
+
} else if constexpr (std::same_as<duration_t, std::chrono::hours>) {
|
|
126
|
+
return "M8[h]";
|
|
127
|
+
} else {
|
|
128
|
+
static_assert(always_false<T>::value, "Unsupported std::chrono::time_point resolution");
|
|
129
|
+
}
|
|
130
|
+
} else if constexpr (is_chrono_duration<T>::value) {
|
|
131
|
+
if constexpr (std::same_as<T, std::chrono::nanoseconds>) {
|
|
132
|
+
return "m8[ns]";
|
|
133
|
+
} else if constexpr (std::same_as<T, std::chrono::microseconds>) {
|
|
134
|
+
return "m8[us]";
|
|
135
|
+
} else if constexpr (std::same_as<T, std::chrono::milliseconds>) {
|
|
136
|
+
return "m8[ms]";
|
|
137
|
+
} else if constexpr (std::same_as<T, std::chrono::seconds>) {
|
|
138
|
+
return "m8[s]";
|
|
139
|
+
} else if constexpr (std::same_as<T, std::chrono::minutes>) {
|
|
140
|
+
return "m8[m]";
|
|
141
|
+
} else if constexpr (std::same_as<T, std::chrono::hours>) {
|
|
142
|
+
return "m8[h]";
|
|
143
|
+
} else {
|
|
144
|
+
static_assert(always_false<T>::value, "Unsupported std::chrono::duration resolution");
|
|
145
|
+
}
|
|
146
|
+
} else if constexpr (std::same_as<T, bool>) {
|
|
147
|
+
return "b1";
|
|
148
|
+
} else if constexpr (std::same_as<T, char>) {
|
|
149
|
+
return "S1";
|
|
150
|
+
} else if constexpr (std::same_as<T, signed char> or std::same_as<T, int8_t>) {
|
|
151
|
+
return "i1";
|
|
152
|
+
} else if constexpr (std::same_as<T, unsigned char> or std::same_as<T, uint8_t> or std::same_as<T, std::byte>) {
|
|
153
|
+
return "u1";
|
|
154
|
+
} else if constexpr (std::same_as<T, short> or std::same_as<T, int16_t>) {
|
|
155
|
+
return "i2";
|
|
156
|
+
} else if constexpr (std::same_as<T, unsigned short> or std::same_as<T, uint16_t>) {
|
|
157
|
+
return "u2";
|
|
158
|
+
} else if constexpr (std::same_as<T, char16_t>) {
|
|
159
|
+
return "U2";
|
|
160
|
+
} else if constexpr (std::same_as<T, int32_t>) {
|
|
161
|
+
return "i4";
|
|
162
|
+
} else if constexpr (std::same_as<T, uint32_t>) {
|
|
163
|
+
return "u4";
|
|
164
|
+
} else if constexpr (std::same_as<T, char32_t>) {
|
|
165
|
+
return "U4";
|
|
166
|
+
} else if constexpr (std::same_as<T, long long> or std::same_as<T, int64_t>) {
|
|
167
|
+
return "i8";
|
|
168
|
+
} else if constexpr (std::same_as<T, unsigned long long> or std::same_as<T, uint64_t>) {
|
|
169
|
+
return "u8";
|
|
170
|
+
} else if constexpr (std::same_as<T, float>) {
|
|
171
|
+
return "f4";
|
|
172
|
+
} else if constexpr (std::same_as<T, double>) {
|
|
173
|
+
return "f8";
|
|
174
|
+
} else if constexpr (std::same_as<T, long double>) {
|
|
175
|
+
if constexpr (sizeof(long double) == 8) {
|
|
176
|
+
return "f8";
|
|
177
|
+
} else if constexpr (sizeof(long double) == 12) {
|
|
178
|
+
return "f12";
|
|
179
|
+
} else if constexpr (sizeof(long double) == 16) {
|
|
180
|
+
return "f16";
|
|
181
|
+
} else {
|
|
182
|
+
return "g";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
template<typename T> consteval bool explicit_layout() {
|
|
189
|
+
using CleanT = std::remove_cvref_t<T>;
|
|
190
|
+
using UncvR = typename underlying_type<CleanT>::type;
|
|
191
|
+
|
|
192
|
+
if constexpr (std::is_union_v<UncvR>) {
|
|
193
|
+
return true;
|
|
194
|
+
} else if constexpr (not std::is_class_v<UncvR>) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
constexpr auto ctx = std::meta::access_context::unchecked();
|
|
199
|
+
|
|
200
|
+
if constexpr (not std::meta::bases_of(^^UncvR, ctx).empty()) {
|
|
201
|
+
return true;
|
|
202
|
+
} else {
|
|
203
|
+
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^UncvR, ctx));
|
|
204
|
+
std::size_t expected_offset = 0;
|
|
205
|
+
bool is_explicit = false;
|
|
206
|
+
|
|
207
|
+
template for (constexpr std::meta::info member : members) {
|
|
208
|
+
using FieldType = typename[:std::meta::type_of(member):];
|
|
209
|
+
constexpr std::string_view field_name = std::meta::identifier_of(member);
|
|
210
|
+
|
|
211
|
+
if constexpr (std::is_union_v<FieldType> and field_name.empty()) {
|
|
212
|
+
is_explicit = true;
|
|
213
|
+
} else {
|
|
214
|
+
constexpr std::size_t actual_offset = std::is_union_v<UncvR> ? 0 : std::meta::offset_of(member).bytes;
|
|
215
|
+
|
|
216
|
+
if (actual_offset != expected_offset) {
|
|
217
|
+
is_explicit = true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
expected_offset = actual_offset + sizeof(FieldType);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (sizeof(UncvR) != expected_offset) {
|
|
225
|
+
is_explicit = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return is_explicit;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
} // namespace detail
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
//----------------------------------------------------------------------------------------------------------------------
|
|
236
|
+
// This function generates a numpy dtype description for a C structure using reflection. It supports nested types,
|
|
237
|
+
// multi-dimensional arrays, enums, named and anonymous unions, and chrono to datetime64 and timedelta64 conversion.
|
|
238
|
+
// Where the numpy default alignment differs from C it will update the dtype to match.
|
|
239
|
+
//
|
|
240
|
+
template <typename T, Endian E = Endian::NONE> std::string desc() {
|
|
241
|
+
using RawT = std::remove_cvref_t<T>;
|
|
242
|
+
using UncvR = typename detail::underlying_type<RawT>::type;
|
|
243
|
+
|
|
244
|
+
if constexpr (std::is_array_v<UncvR>) {
|
|
245
|
+
using ElementType = std::remove_all_extents_t<UncvR>;
|
|
246
|
+
|
|
247
|
+
if constexpr (std::same_as<ElementType, char>) {
|
|
248
|
+
return "'S" + std::to_string(sizeof(UncvR)) + "'";
|
|
249
|
+
} else if constexpr (std::same_as<ElementType, char16_t>) {
|
|
250
|
+
return "'" + std::string(detail::endian_prefix<E>()) + "U" + std::to_string(sizeof(UncvR) / 2) + "'";
|
|
251
|
+
} else if constexpr (std::same_as<ElementType, char32_t>) {
|
|
252
|
+
return "'" + std::string(detail::endian_prefix<E>()) + "U" + std::to_string(sizeof(UncvR) / 4) + "'";
|
|
253
|
+
} else {
|
|
254
|
+
return "(" + desc<ElementType, E>() + std::string(detail::array_dims<UncvR>()) + ")";
|
|
255
|
+
}
|
|
256
|
+
} else if constexpr (constexpr std::string_view scalar = detail::np_scalar_type<UncvR>(); not scalar.empty()) {
|
|
257
|
+
if (scalar == "b1" or scalar == "S1" or scalar == "i1" or scalar == "u1") {
|
|
258
|
+
return "'" + std::string(scalar) + "'";
|
|
259
|
+
} else {
|
|
260
|
+
return "'" + std::string(detail::endian_prefix<E>()) + std::string(scalar) + "'";
|
|
261
|
+
}
|
|
262
|
+
} else if constexpr (std::is_union_v<UncvR> or std::is_class_v<UncvR>) {
|
|
263
|
+
constexpr auto ctx = std::meta::access_context::unchecked();
|
|
264
|
+
constexpr bool has_bases = not std::meta::bases_of(^^UncvR, ctx).empty();
|
|
265
|
+
|
|
266
|
+
if constexpr (not has_bases and not detail::explicit_layout<UncvR>()) { // default numpy layout
|
|
267
|
+
static constexpr auto members = std::define_static_array(std::meta::nonstatic_data_members_of(^^UncvR, ctx));
|
|
268
|
+
std::string list = "[";
|
|
269
|
+
bool is_first = true;
|
|
270
|
+
|
|
271
|
+
template for (constexpr std::meta::info member : members) {
|
|
272
|
+
constexpr std::string_view field_name = std::meta::identifier_of(member);
|
|
273
|
+
using FieldType = typename[:std::meta::type_of(member):];
|
|
274
|
+
|
|
275
|
+
if (not is_first) {
|
|
276
|
+
list += ", ";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
is_first = false;
|
|
280
|
+
list += "('" + std::string(field_name) + "', " + desc<FieldType, E>() + ")";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
list += "]";
|
|
284
|
+
return list;
|
|
285
|
+
} else { // explicit dict layout due to base classes, padding, or unions
|
|
286
|
+
std::string names = "'names': [";
|
|
287
|
+
std::string formats = "'formats': [";
|
|
288
|
+
std::string offsets = "'offsets': [";
|
|
289
|
+
bool is_first = true;
|
|
290
|
+
|
|
291
|
+
if constexpr (has_bases) {
|
|
292
|
+
static constexpr auto bases = std::define_static_array(std::meta::bases_of(^^UncvR, ctx));
|
|
293
|
+
|
|
294
|
+
template for (constexpr std::meta::info base : bases) {
|
|
295
|
+
using BaseType = typename[:std::meta::type_of(base):];
|
|
296
|
+
constexpr std::size_t base_offset = std::meta::offset_of(base).bytes;
|
|
297
|
+
static constexpr auto base_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^BaseType, ctx));
|
|
298
|
+
|
|
299
|
+
template for (constexpr std::meta::info bm : base_members) {
|
|
300
|
+
if (not is_first) {
|
|
301
|
+
names += ", ";
|
|
302
|
+
formats += ", ";
|
|
303
|
+
offsets += ", ";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
is_first = false;
|
|
307
|
+
names += "'" + std::string(std::meta::identifier_of(bm)) + "'";
|
|
308
|
+
formats += desc<typename[:std::meta::type_of(bm):], E>();
|
|
309
|
+
offsets += std::to_string(base_offset + (std::is_union_v<BaseType> ? 0 : std::meta::offset_of(bm).bytes));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static constexpr auto local_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^UncvR, ctx));
|
|
315
|
+
|
|
316
|
+
template for (constexpr std::meta::info member : local_members) {
|
|
317
|
+
constexpr std::string_view field_name = std::meta::identifier_of(member);
|
|
318
|
+
using FieldType = typename[:std::meta::type_of(member):];
|
|
319
|
+
constexpr std::size_t offset = std::is_union_v<UncvR> ? 0 : std::meta::offset_of(member).bytes;
|
|
320
|
+
|
|
321
|
+
if constexpr (std::is_union_v<FieldType> and field_name.empty()) { // flatten anonymous unions
|
|
322
|
+
static constexpr auto union_members = std::define_static_array(std::meta::nonstatic_data_members_of(^^FieldType, ctx));
|
|
323
|
+
|
|
324
|
+
template for (constexpr std::meta::info u_member : union_members) {
|
|
325
|
+
constexpr std::string_view u_name = std::meta::identifier_of(u_member);
|
|
326
|
+
using UFieldType = typename[:std::meta::type_of(u_member):];
|
|
327
|
+
|
|
328
|
+
if (not is_first) {
|
|
329
|
+
names += ", ";
|
|
330
|
+
formats += ", ";
|
|
331
|
+
offsets += ", ";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
is_first = false;
|
|
335
|
+
names += "'" + std::string(u_name) + "'";
|
|
336
|
+
formats += desc<UFieldType, E>();
|
|
337
|
+
offsets += std::to_string(offset);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
if (not is_first) {
|
|
341
|
+
names += ", ";
|
|
342
|
+
formats += ", ";
|
|
343
|
+
offsets += ", ";
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
is_first = false;
|
|
347
|
+
names += "'" + std::string(field_name) + "'";
|
|
348
|
+
formats += desc<FieldType, E>();
|
|
349
|
+
offsets += std::to_string(offset);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
names += "]";
|
|
354
|
+
formats += "]";
|
|
355
|
+
offsets += "]";
|
|
356
|
+
return "{" + names + ", " + formats + ", " + offsets + ", 'itemsize': " + std::to_string(sizeof(UncvR)) + "}";
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
static_assert(detail::always_false<UncvR>::value, "Unsupported member type found during dtype conversion");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
} // namespace pydtype
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pydtype-cpp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Compile time C++26 header exposing C structs to numpy dtypes
|
|
5
|
+
Author: Paul Scruby
|
|
6
|
+
Classifier: Programming Language :: C++
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# pydtype
|
|
13
|
+
|
|
14
|
+
Compile time C++26 exposing C structs to numpy dtypes. Supports inheritance, nested types, multi-dimensional arrays,
|
|
15
|
+
enums, named and anonymous unions, and chrono to datetime64 and timedelta64 conversion. Where the numpy default
|
|
16
|
+
alignment differs from C it will update the dtype to match.
|
|
17
|
+
|
|
18
|
+
This is currrently an early version that wrote during my day job as a quant which I've released because it maybe useful
|
|
19
|
+
to the wider numpy community. I've only had time to test it with gcc16.2 on linux. To compile it I had to include the
|
|
20
|
+
`-std=c++26 -freflection` flags.
|
|
21
|
+
|
|
22
|
+
I welcome contributions, especially to get it work on other compilers and platforms.
|
|
23
|
+
|
|
24
|
+
Thanks,
|
|
25
|
+
|
|
26
|
+
Paul
|
|
27
|
+
|
|
28
|
+
## Examples
|
|
29
|
+
|
|
30
|
+
```c++
|
|
31
|
+
#include <pydtype/desc.hpp>
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
struct Simple {
|
|
35
|
+
std::uint32_t len;
|
|
36
|
+
float coords[3];
|
|
37
|
+
double value;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`pydtype::desc<Simple>()` will produce:
|
|
43
|
+
```
|
|
44
|
+
[('len', 'u4'), ('coords', ('f4', 3)), ('value', 'f8')]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Here is a more complex example:
|
|
48
|
+
|
|
49
|
+
```c++
|
|
50
|
+
|
|
51
|
+
#include <pydtype/desc.hpp>
|
|
52
|
+
|
|
53
|
+
enum class Type
|
|
54
|
+
: std::uint8_t {
|
|
55
|
+
LINE = 0,
|
|
56
|
+
TRANSFORM,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
#pragma pack(push, 1)
|
|
60
|
+
|
|
61
|
+
struct Point {
|
|
62
|
+
double x;
|
|
63
|
+
double y;
|
|
64
|
+
double z;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Base {
|
|
69
|
+
public:
|
|
70
|
+
std::chrono::time_point<std::int64_t, std::chrono::nanoseconds> time;
|
|
71
|
+
char label[12];
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Complex
|
|
76
|
+
: public Base {
|
|
77
|
+
public:
|
|
78
|
+
Type type;
|
|
79
|
+
|
|
80
|
+
union {
|
|
81
|
+
Point line[2];
|
|
82
|
+
float transform[4][4];
|
|
83
|
+
} data;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
#pragma pack(pop)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`pydtype::desc<Complex. pydtype::Endian::LITTLE>()` will produce:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
{'names': ['time', 'label', 'type', 'data'], 'formats': ['<M8[ns]', 'S12', 'u1', {
|
|
93
|
+
'names': ['line', 'transform'], 'formats': [([('x', '<f8'), ('y', '<f8'),('z', '<f8')], 2), ('<f4', 4, 4)],
|
|
94
|
+
'offsets': [0, 0],'itemsize': 64}],
|
|
95
|
+
'offsets': [0, 8, 20, 21], 'itemsize': 85}
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Here is another example combining pydtype with the [pybind11](https://github.com/pybind/pybind11) library, returning raw
|
|
100
|
+
C struct data from C++ to python so it can be accessed directly by numpy.
|
|
101
|
+
|
|
102
|
+
```c++
|
|
103
|
+
#include <pybind11/pybind11.h>
|
|
104
|
+
#include <pybind11/eval.h>
|
|
105
|
+
#include <pybind11/numpy.h>
|
|
106
|
+
#include <pydtype/desc.hpp>
|
|
107
|
+
|
|
108
|
+
namespace py = pybind11;
|
|
109
|
+
|
|
110
|
+
auto ptr = new Simple[10];
|
|
111
|
+
auto cap = py::capsule(ptr, [](void *v) { delete[] reinterpret_cast<Simple*>(v); });
|
|
112
|
+
auto dtype = py::dtype::from_args(py::eval(pydtype::desc<Simple>()));
|
|
113
|
+
return py::array(dtype, {10}, ptr, cap);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Installation
|
|
117
|
+
|
|
118
|
+
You can use pip, uv and conda to install the pydtype header file into your current python environment.
|
|
119
|
+
|
|
120
|
+
### Installing from a local repos (development)
|
|
121
|
+
|
|
122
|
+
### pip
|
|
123
|
+
```commandline
|
|
124
|
+
git clone https://github.com/blackbox-tech/pydtype.git
|
|
125
|
+
cd pydtype
|
|
126
|
+
python -m pip install .
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### uv
|
|
130
|
+
```commandline
|
|
131
|
+
git clone https://github.com/blackbox-tech/pydtype.git
|
|
132
|
+
cd pydtype
|
|
133
|
+
uv pip install .
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### conda
|
|
137
|
+
```commandline
|
|
138
|
+
git clone https://github.com/blackbox-tech/pydtype.git
|
|
139
|
+
cd pydtype/conda
|
|
140
|
+
conda build .
|
|
141
|
+
conda install --use-local pydtype
|
|
142
|
+
```
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
pydtype/desc.hpp,sha256=MyJdiaGjjED3fGeNliXHqnyMTh86JnnumzHNR-fhkHE,14967
|
|
2
|
+
pydtype_cpp-0.1.0.data/data/include/pydtype/desc.hpp,sha256=MyJdiaGjjED3fGeNliXHqnyMTh86JnnumzHNR-fhkHE,14967
|
|
3
|
+
pydtype_cpp-0.1.0.dist-info/METADATA,sha256=vub40yuyAp9N9CFentz1rvnTdX5KP1ZH5phd9T7z0uw,3285
|
|
4
|
+
pydtype_cpp-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
5
|
+
pydtype_cpp-0.1.0.dist-info/RECORD,,
|