pydtype-cpp 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.
@@ -0,0 +1,5 @@
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
4
+ # Editor-based HTTP Client requests
5
+ /httpRequests/
@@ -0,0 +1,6 @@
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
@@ -0,0 +1,9 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/pydtype.iml" filepath="$PROJECT_DIR$/.idea/pydtype.iml" />
6
+ <module fileurl="file://$PROJECT_DIR$/.idea/pydtype@1.iml" filepath="$PROJECT_DIR$/.idea/pydtype@1.iml" />
7
+ </modules>
8
+ </component>
9
+ </project>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$" />
5
+ <orderEntry type="sourceFolder" forTests="false" />
6
+ </component>
7
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,119 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AutoImportSettings">
4
+ <option name="autoReloadType" value="SELECTIVE" />
5
+ </component>
6
+ <component name="ChangeListManager">
7
+ <list default="true" id="10280a4e-8990-4eff-94b1-57a1975929de" name="Changes" comment="">
8
+ <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
9
+ </list>
10
+ <option name="SHOW_DIALOG" value="false" />
11
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
12
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
13
+ <option name="LAST_RESOLUTION" value="IGNORE" />
14
+ </component>
15
+ <component name="Git.Settings">
16
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
17
+ </component>
18
+ <component name="NextEditCompletionFeaturesState">
19
+ <decayedCancelled>
20
+ <entry key="MS100" value="1.0" />
21
+ <entry key="MS500" value="1.0" />
22
+ <entry key="S2" value="1.0" />
23
+ <entry key="S5" value="1.0" />
24
+ <entry key="S10" value="1.0" />
25
+ <entry key="S30" value="1.0" />
26
+ <entry key="S60" value="1.0000000000332818" />
27
+ <entry key="M2" value="1.0000095256525936" />
28
+ <entry key="M5" value="1.0614427211604982" />
29
+ <entry key="M10" value="3.196249607874741" />
30
+ <entry key="M15" value="9.111399959265816" />
31
+ <entry key="M30" value="38.853030710943514" />
32
+ <entry key="H1" value="106.79374674894471" />
33
+ <entry key="H2" value="197.82848538401652" />
34
+ <entry key="H4" value="277.48231820513087" />
35
+ <entry key="D1" value="373.4882815964569" />
36
+ <entry key="W1" value="393.5397778892723" />
37
+ </decayedCancelled>
38
+ <decayedSelected>
39
+ <entry key="MS100" value="0.0" />
40
+ <entry key="MS500" value="0.0" />
41
+ <entry key="S2" value="0.0" />
42
+ <entry key="S5" value="2.8101433695149424E-208" />
43
+ <entry key="S10" value="1.6763482244196597E-104" />
44
+ <entry key="S30" value="2.5593012743645917E-35" />
45
+ <entry key="S60" value="5.058953720251442E-18" />
46
+ <entry key="M2" value="2.249211799778428E-9" />
47
+ <entry key="M5" value="3.4739165658842264E-4" />
48
+ <entry key="M10" value="0.01872307429518704" />
49
+ <entry key="M15" value="0.07252436535566131" />
50
+ <entry key="M30" value="0.32838990699427145" />
51
+ <entry key="H1" value="0.8653639116717755" />
52
+ <entry key="H2" value="1.5515966108213575" />
53
+ <entry key="H4" value="2.1373905494251955" />
54
+ <entry key="D1" value="2.8315904602678583" />
55
+ <entry key="W1" value="2.9752509025309806" />
56
+ </decayedSelected>
57
+ <decayedShown>
58
+ <entry key="MS100" value="0.668963777393056" />
59
+ <entry key="MS500" value="0.9227424926537983" />
60
+ <entry key="S2" value="0.9800994153418706" />
61
+ <entry key="S5" value="0.9919917311220098" />
62
+ <entry key="S10" value="0.9959878167538044" />
63
+ <entry key="S30" value="0.9986608129632579" />
64
+ <entry key="S60" value="0.9993301821869297" />
65
+ <entry key="M2" value="0.9996745616996886" />
66
+ <entry key="M5" value="1.0616509231534412" />
67
+ <entry key="M10" value="3.2147814469933187" />
68
+ <entry key="M15" value="9.183550690638386" />
69
+ <entry key="M30" value="39.18058837655255" />
70
+ <entry key="H1" value="107.65785069158214" />
71
+ <entry key="H2" value="199.3787654049646" />
72
+ <entry key="H4" value="279.61870403270444" />
73
+ <entry key="D1" value="376.3196273310186" />
74
+ <entry key="W1" value="396.5149913928807" />
75
+ </decayedShown>
76
+ </component>
77
+ <component name="ProjectColorInfo">{
78
+ &quot;associatedIndex&quot;: 3,
79
+ &quot;fromUser&quot;: false
80
+ }</component>
81
+ <component name="ProjectId" id="3LgfrKlIWEvEkUgh2uBDz3vp57F" />
82
+ <component name="ProjectViewState">
83
+ <option name="hideEmptyMiddlePackages" value="true" />
84
+ <option name="showLibraryContents" value="true" />
85
+ </component>
86
+ <component name="PropertiesComponent"><![CDATA[{
87
+ "keyToString": {
88
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
89
+ "RunOnceActivity.ShowReadmeOnStart": "true",
90
+ "RunOnceActivity.git.unshallow": "true",
91
+ "RunOnceActivity.typescript.service.memoryLimit.init": "true",
92
+ "codeWithMe.voiceChat.enabledByDefault": "false",
93
+ "git-widget-placeholder": "main",
94
+ "last_opened_file_path": "/home/paul/mp-setup",
95
+ "nodejs_package_manager_path": "npm",
96
+ "python.packages.toolwindow.project.migrated.to.right.top.v2": "true"
97
+ }
98
+ }]]></component>
99
+ <component name="SharedIndexes">
100
+ <attachedChunks>
101
+ <set>
102
+ <option value="bundled-python-sdk-ebb3072d7bdf-5bd6d13a2a75-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-262.11879.98" />
103
+ </set>
104
+ </attachedChunks>
105
+ </component>
106
+ <component name="TaskManager">
107
+ <task active="true" id="Default" summary="Default task">
108
+ <changelist id="10280a4e-8990-4eff-94b1-57a1975929de" name="Changes" comment="" />
109
+ <created>1793880830810</created>
110
+ <option name="number" value="Default" />
111
+ <option name="presentableId" value="Default" />
112
+ <updated>1793880830810</updated>
113
+ </task>
114
+ <servers />
115
+ </component>
116
+ <component name="TypeScriptGeneratedFilesManager">
117
+ <option name="version" value="3" />
118
+ </component>
119
+ </project>
@@ -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,131 @@
1
+ # pydtype
2
+
3
+ Compile time C++26 exposing C structs to numpy dtypes. Supports inheritance, nested types, multi-dimensional arrays,
4
+ enums, named and anonymous unions, and chrono to datetime64 and timedelta64 conversion. Where the numpy default
5
+ alignment differs from C it will update the dtype to match.
6
+
7
+ This is currrently an early version that wrote during my day job as a quant which I've released because it maybe useful
8
+ 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
9
+ `-std=c++26 -freflection` flags.
10
+
11
+ I welcome contributions, especially to get it work on other compilers and platforms.
12
+
13
+ Thanks,
14
+
15
+ Paul
16
+
17
+ ## Examples
18
+
19
+ ```c++
20
+ #include <pydtype/desc.hpp>
21
+
22
+
23
+ struct Simple {
24
+ std::uint32_t len;
25
+ float coords[3];
26
+ double value;
27
+ };
28
+
29
+ ```
30
+
31
+ `pydtype::desc<Simple>()` will produce:
32
+ ```
33
+ [('len', 'u4'), ('coords', ('f4', 3)), ('value', 'f8')]
34
+ ```
35
+
36
+ Here is a more complex example:
37
+
38
+ ```c++
39
+
40
+ #include <pydtype/desc.hpp>
41
+
42
+ enum class Type
43
+ : std::uint8_t {
44
+ LINE = 0,
45
+ TRANSFORM,
46
+ };
47
+
48
+ #pragma pack(push, 1)
49
+
50
+ struct Point {
51
+ double x;
52
+ double y;
53
+ double z;
54
+ };
55
+
56
+
57
+ class Base {
58
+ public:
59
+ std::chrono::time_point<std::int64_t, std::chrono::nanoseconds> time;
60
+ char label[12];
61
+ };
62
+
63
+
64
+ class Complex
65
+ : public Base {
66
+ public:
67
+ Type type;
68
+
69
+ union {
70
+ Point line[2];
71
+ float transform[4][4];
72
+ } data;
73
+ };
74
+
75
+ #pragma pack(pop)
76
+ ```
77
+
78
+ `pydtype::desc<Complex. pydtype::Endian::LITTLE>()` will produce:
79
+
80
+ ```
81
+ {'names': ['time', 'label', 'type', 'data'], 'formats': ['<M8[ns]', 'S12', 'u1', {
82
+ 'names': ['line', 'transform'], 'formats': [([('x', '<f8'), ('y', '<f8'),('z', '<f8')], 2), ('<f4', 4, 4)],
83
+ 'offsets': [0, 0],'itemsize': 64}],
84
+ 'offsets': [0, 8, 20, 21], 'itemsize': 85}
85
+
86
+ ```
87
+
88
+ Here is another example combining pydtype with the [pybind11](https://github.com/pybind/pybind11) library, returning raw
89
+ C struct data from C++ to python so it can be accessed directly by numpy.
90
+
91
+ ```c++
92
+ #include <pybind11/pybind11.h>
93
+ #include <pybind11/eval.h>
94
+ #include <pybind11/numpy.h>
95
+ #include <pydtype/desc.hpp>
96
+
97
+ namespace py = pybind11;
98
+
99
+ auto ptr = new Simple[10];
100
+ auto cap = py::capsule(ptr, [](void *v) { delete[] reinterpret_cast<Simple*>(v); });
101
+ auto dtype = py::dtype::from_args(py::eval(pydtype::desc<Simple>()));
102
+ return py::array(dtype, {10}, ptr, cap);
103
+ ```
104
+
105
+ ## Installation
106
+
107
+ You can use pip, uv and conda to install the pydtype header file into your current python environment.
108
+
109
+ ### Installing from a local repos (development)
110
+
111
+ ### pip
112
+ ```commandline
113
+ git clone https://github.com/blackbox-tech/pydtype.git
114
+ cd pydtype
115
+ python -m pip install .
116
+ ```
117
+
118
+ ### uv
119
+ ```commandline
120
+ git clone https://github.com/blackbox-tech/pydtype.git
121
+ cd pydtype
122
+ uv pip install .
123
+ ```
124
+
125
+ ### conda
126
+ ```commandline
127
+ git clone https://github.com/blackbox-tech/pydtype.git
128
+ cd pydtype/conda
129
+ conda build .
130
+ conda install --use-local pydtype
131
+ ```
@@ -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,27 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pydtype-cpp"
7
+ version = "0.1.0"
8
+ description = "Compile time C++26 header exposing C structs to numpy dtypes"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ authors = [
12
+ { name = "Paul Scruby" }
13
+ ]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: C++",
17
+ "Topic :: Software Development :: Libraries :: Python Modules",
18
+ ]
19
+
20
+ [tool.hatch.build.targets.wheel]
21
+ pure_python = false
22
+
23
+ [tool.hatch.build.targets.wheel.force-include]
24
+ "include/pydtype" = "pydtype"
25
+
26
+ [tool.hatch.build.targets.wheel.shared-data]
27
+ "include/pydtype/desc.hpp" = "include/pydtype/desc.hpp"
@@ -0,0 +1,27 @@
1
+ {% set name = "pydtype" %}
2
+ {% set version = "0.1.0" %}
3
+
4
+ package:
5
+ name: {{ name }}
6
+ version: {{ version }}
7
+
8
+ source:
9
+ git_url: https://github.com/blackbox-tech/pydtype.git
10
+ git_rev: {{ version }}
11
+
12
+ build:
13
+ noarch: generic
14
+ number: 0
15
+ script:
16
+ - {{ PYTHON }} -c "import shutil, os; dst = os.path.join(os.environ['PREFIX'], 'include', 'pydtype'); os.makedirs(dst, exist_ok=True); shutil.copy('include/pydtype/desc.hpp', dst)"
17
+
18
+ requirements:
19
+ host:
20
+ - python >=3.8
21
+ run:
22
+ - python >=3.8
23
+
24
+ about:
25
+ home: https://github.com/blackbox-tech/pydtype
26
+ license: MIT
27
+ summary: 'Compile time C++26 header exposing C structs to numpy dtypes'