C++ YASMIN (Yet Another State MachINe)
Loading...
Searching...
No Matches
blackboard_key_info_py.hpp
Go to the documentation of this file.
1// Copyright (C) 2026 Miguel Ángel González Santamarta
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <https://www.gnu.org/licenses/>.
15
16#ifndef YASMIN__BLACKBOARD_KEY_INFO_PY_HPP_
17#define YASMIN__BLACKBOARD_KEY_INFO_PY_HPP_
18
19#include <pybind11/pybind11.h>
20#include <pybind11/stl.h>
21
22#include <cstdint>
23#include <stdexcept>
24#include <string>
25#include <typeinfo>
26#include <unordered_map>
27#include <vector>
28
30#include "yasmin/state.hpp"
31#include "yasmin/types.hpp"
32
33namespace py = pybind11;
34
35namespace yasmin {
36
37namespace detail {
38
39using StringVector = std::vector<std::string>;
40using IntVector = std::vector<std::int64_t>;
41using FloatVector = std::vector<double>;
42using BoolVector = std::vector<bool>;
43
44using StringDict = std::unordered_map<std::string, std::string>;
45using IntDict = std::unordered_map<std::string, std::int64_t>;
46using FloatDict = std::unordered_map<std::string, double>;
47using BoolDict = std::unordered_map<std::string, bool>;
48
49inline std::vector<uint8_t> py_buffer_to_vector(const py::handle &value) {
50 char *data = nullptr;
51 py::ssize_t size = 0;
52
53 if (PyBytes_Check(value.ptr()) != 0) {
54 if (PyBytes_AsStringAndSize(value.ptr(), &data, &size) != 0) {
55 throw py::error_already_set();
56 }
57 } else if (PyByteArray_Check(value.ptr()) != 0) {
58 data = PyByteArray_AsString(value.ptr());
59 size = PyByteArray_Size(value.ptr());
60 } else {
61 throw std::runtime_error("Expected bytes or bytearray object");
62 }
63
64 return std::vector<uint8_t>(reinterpret_cast<uint8_t *>(data),
65 reinterpret_cast<uint8_t *>(data) + size);
66}
67
68template <typename ByteT>
69inline py::bytes byte_vector_to_py_bytes(const std::vector<ByteT> &value) {
70 return py::bytes(reinterpret_cast<const char *>(value.data()),
71 static_cast<py::ssize_t>(value.size()));
72}
73
74inline bool is_python_bytes_like(const py::handle &value) {
75 return PyBytes_Check(value.ptr()) != 0 || PyByteArray_Check(value.ptr()) != 0;
76}
77
78inline bool is_python_int_like(const py::handle &value) {
79 return py::isinstance<py::int_>(value) && !py::isinstance<py::bool_>(value);
80}
81
82inline bool is_python_float_like(const py::handle &value) {
83 return py::isinstance<py::float_>(value);
84}
85
86inline bool is_python_number_like(const py::handle &value) {
87 return is_python_int_like(value) || is_python_float_like(value);
88}
89
90inline bool is_python_sequence_like(const py::handle &value) {
91 return py::isinstance<py::list>(value) || py::isinstance<py::tuple>(value);
92}
93
94template <typename Predicate>
95inline bool sequence_matches(const py::sequence &seq, Predicate pred) {
96 for (auto item : seq) {
97 if (!pred(item)) {
98 return false;
99 }
100 }
101 return true;
102}
103
104template <typename T>
105inline std::vector<T> sequence_to_vector(const py::sequence &seq) {
106 std::vector<T> result;
107 result.reserve(static_cast<std::size_t>(py::len(seq)));
108
109 for (auto item : seq) {
110 result.push_back(py::cast<T>(item));
111 }
112
113 return result;
114}
115
116inline bool dict_has_only_string_keys(const py::dict &dict) {
117 for (auto item : dict) {
118 if (!py::isinstance<py::str>(item.first)) {
119 return false;
120 }
121 }
122 return true;
123}
124
125template <typename Predicate>
126inline bool dict_values_match(const py::dict &dict, Predicate pred) {
127 for (auto item : dict) {
128 if (!pred(item.second)) {
129 return false;
130 }
131 }
132 return true;
133}
134
135template <typename T>
136inline std::unordered_map<std::string, T>
137dict_to_unordered_map(const py::dict &dict) {
138 std::unordered_map<std::string, T> result;
139 result.reserve(static_cast<std::size_t>(py::len(dict)));
140
141 for (auto item : dict) {
142 result.emplace(py::cast<std::string>(item.first), py::cast<T>(item.second));
143 }
144
145 return result;
146}
147
148template <typename T> inline bool is_exact_cpp_type(const std::string &type) {
149 return type == demangle_type(typeid(T).name());
150}
151
152} // namespace detail
153
161inline BlackboardKeyInfo
162blackboard_key_info_from_pyobject(const std::string &key_name,
163 py::object value) {
164 using namespace detail;
165
166 if (py::isinstance<py::bool_>(value)) {
167 return BlackboardKeyInfo(key_name, "", value.cast<bool>());
168 }
169
170 if (is_python_int_like(value)) {
171 return BlackboardKeyInfo(key_name, "", value.cast<std::int64_t>());
172 }
173
174 if (py::isinstance<py::float_>(value)) {
175 return BlackboardKeyInfo(key_name, "", value.cast<double>());
176 }
177
178 if (is_python_bytes_like(value)) {
179 return BlackboardKeyInfo(key_name, "", py_buffer_to_vector(value));
180 }
181
182 if (py::isinstance<py::str>(value)) {
183 return BlackboardKeyInfo(key_name, "", value.cast<std::string>());
184 }
185
186 if (is_python_sequence_like(value)) {
187 py::sequence seq = value.cast<py::sequence>();
188
189 if (py::len(seq) == 0) {
190 BlackboardKeyInfo info(key_name);
191 info.has_default = true;
192 info.default_value_type = "py::object";
193 auto stored = std::make_shared<py::object>(value);
194 info.default_value = stored;
195 info.inject_default = [stored](Blackboard &bb, const std::string &key) {
196 py::gil_scoped_acquire gil;
197 bb.set<py::object>(key, *stored);
198 };
199 return info;
200 }
201
202 if (sequence_matches(seq, [](const py::handle &item) {
203 return py::isinstance<py::bool_>(item);
204 })) {
205 return BlackboardKeyInfo(key_name, "", sequence_to_vector<bool>(seq));
206 }
207
208 if (sequence_matches(seq, [](const py::handle &item) {
209 return is_python_int_like(item);
210 })) {
211 return BlackboardKeyInfo(key_name, "",
212 sequence_to_vector<std::int64_t>(seq));
213 }
214
215 if (sequence_matches(seq, [](const py::handle &item) {
216 return is_python_number_like(item);
217 })) {
218 return BlackboardKeyInfo(key_name, "", sequence_to_vector<double>(seq));
219 }
220
221 if (sequence_matches(seq, [](const py::handle &item) {
222 return py::isinstance<py::str>(item);
223 })) {
224 return BlackboardKeyInfo(key_name, "",
225 sequence_to_vector<std::string>(seq));
226 }
227 }
228
229 if (py::isinstance<py::dict>(value)) {
230 py::dict dict = value.cast<py::dict>();
231
232 if (py::len(dict) != 0 && dict_has_only_string_keys(dict)) {
233 if (dict_values_match(dict, [](const py::handle &item) {
234 return py::isinstance<py::bool_>(item);
235 })) {
236 return BlackboardKeyInfo(key_name, "",
237 dict_to_unordered_map<bool>(dict));
238 }
239
240 if (dict_values_match(dict, [](const py::handle &item) {
241 return is_python_int_like(item);
242 })) {
243 return BlackboardKeyInfo(key_name, "",
244 dict_to_unordered_map<std::int64_t>(dict));
245 }
246
247 if (dict_values_match(dict, [](const py::handle &item) {
248 return is_python_number_like(item);
249 })) {
250 return BlackboardKeyInfo(key_name, "",
251 dict_to_unordered_map<double>(dict));
252 }
253
254 if (dict_values_match(dict, [](const py::handle &item) {
255 return py::isinstance<py::str>(item);
256 })) {
257 return BlackboardKeyInfo(key_name, "",
258 dict_to_unordered_map<std::string>(dict));
259 }
260 }
261 }
262
263 BlackboardKeyInfo info(key_name);
264 info.has_default = true;
265 info.default_value_type = "py::object";
266
267 auto stored = std::make_shared<py::object>(value);
268 info.default_value = stored;
269 info.inject_default = [stored](Blackboard &bb, const std::string &key) {
270 py::gil_scoped_acquire gil;
271 bb.set<py::object>(key, *stored);
272 };
273
274 return info;
275}
276
283inline py::object
285 using namespace detail;
286
287 if (!info.has_default) {
288 return py::none();
289 }
290
291 const std::string &type = info.default_value_type;
292
293 if (is_exact_cpp_type<std::vector<uint8_t>>(type)) {
294 return byte_vector_to_py_bytes(
295 info.get_default_value<std::vector<uint8_t>>());
296 }
297 if (is_exact_cpp_type<std::vector<unsigned char>>(type)) {
298 return byte_vector_to_py_bytes(
299 info.get_default_value<std::vector<unsigned char>>());
300 }
301 if (is_exact_cpp_type<std::vector<char>>(type)) {
302 return byte_vector_to_py_bytes(info.get_default_value<std::vector<char>>());
303 }
304
305 if (is_exact_cpp_type<StringVector>(type)) {
306 return py::cast(info.get_default_value<StringVector>());
307 }
308 if (is_exact_cpp_type<IntVector>(type) ||
309 is_exact_cpp_type<std::vector<int>>(type) ||
310 is_exact_cpp_type<std::vector<long>>(type) ||
311 is_exact_cpp_type<std::vector<long long>>(type)) {
312 if (is_exact_cpp_type<IntVector>(type)) {
313 return py::cast(info.get_default_value<IntVector>());
314 }
315 if (is_exact_cpp_type<std::vector<int>>(type)) {
316 return py::cast(info.get_default_value<std::vector<int>>());
317 }
318 if (is_exact_cpp_type<std::vector<long>>(type)) {
319 return py::cast(info.get_default_value<std::vector<long>>());
320 }
321 return py::cast(info.get_default_value<std::vector<long long>>());
322 }
323 if (is_exact_cpp_type<FloatVector>(type) ||
324 is_exact_cpp_type<std::vector<float>>(type)) {
325 if (is_exact_cpp_type<FloatVector>(type)) {
326 return py::cast(info.get_default_value<FloatVector>());
327 }
328 return py::cast(info.get_default_value<std::vector<float>>());
329 }
330 if (is_exact_cpp_type<BoolVector>(type)) {
331 return py::cast(info.get_default_value<BoolVector>());
332 }
333
334 if (is_exact_cpp_type<StringDict>(type)) {
335 return py::cast(info.get_default_value<StringDict>());
336 }
337 if (is_exact_cpp_type<IntDict>(type) ||
338 is_exact_cpp_type<std::unordered_map<std::string, int>>(type) ||
339 is_exact_cpp_type<std::unordered_map<std::string, long>>(type) ||
340 is_exact_cpp_type<std::unordered_map<std::string, long long>>(type)) {
341 if (is_exact_cpp_type<IntDict>(type)) {
342 return py::cast(info.get_default_value<IntDict>());
343 }
344 if (is_exact_cpp_type<std::unordered_map<std::string, int>>(type)) {
345 return py::cast(
346 info.get_default_value<std::unordered_map<std::string, int>>());
347 }
348 if (is_exact_cpp_type<std::unordered_map<std::string, long>>(type)) {
349 return py::cast(
350 info.get_default_value<std::unordered_map<std::string, long>>());
351 }
352 return py::cast(
353 info.get_default_value<std::unordered_map<std::string, long long>>());
354 }
355 if (is_exact_cpp_type<FloatDict>(type) ||
356 is_exact_cpp_type<std::unordered_map<std::string, float>>(type)) {
357 if (is_exact_cpp_type<FloatDict>(type)) {
358 return py::cast(info.get_default_value<FloatDict>());
359 }
360 return py::cast(
361 info.get_default_value<std::unordered_map<std::string, float>>());
362 }
363 if (is_exact_cpp_type<BoolDict>(type)) {
364 return py::cast(info.get_default_value<BoolDict>());
365 }
366
367 if (is_exact_cpp_type<std::string>(type)) {
368 return py::cast(info.get_default_value<std::string>());
369 }
370 if (is_exact_cpp_type<std::int64_t>(type)) {
371 return py::cast(info.get_default_value<std::int64_t>());
372 }
373 if (is_exact_cpp_type<int>(type)) {
374 return py::cast(info.get_default_value<int>());
375 }
376 if (is_exact_cpp_type<long>(type)) {
377 return py::cast(info.get_default_value<long>());
378 }
379 if (is_exact_cpp_type<long long>(type)) {
380 return py::cast(info.get_default_value<long long>());
381 }
382 if (is_exact_cpp_type<float>(type)) {
383 return py::cast(info.get_default_value<float>());
384 }
385 if (is_exact_cpp_type<double>(type)) {
386 return py::cast(info.get_default_value<double>());
387 }
388 if (is_exact_cpp_type<bool>(type)) {
389 return py::cast(info.get_default_value<bool>());
390 }
391 if (type == "py::object") {
392 return info.get_default_value<py::object>();
393 }
394
395 throw std::runtime_error("Unsupported BlackboardKeyInfo default value type "
396 "for Python conversion: " +
397 type);
398}
399
400} // namespace yasmin
401
402namespace yasmin {
403
409 static BlackboardKeyInfo from_pyobject(const std::string &key_name,
410 py::object value) {
411 return blackboard_key_info_from_pyobject(key_name, value);
412 }
413
414 static py::object get_py_default_value(const BlackboardKeyInfo &info) {
416 }
417};
418
419} // namespace yasmin
420
421#endif // YASMIN__BLACKBOARD_KEY_INFO_PY_HPP_
A thread-safe storage for key-value pairs of varying types.
Definition blackboard.hpp:67
void set(const std::string &name, T value)
Set a value in the blackboard.
Definition blackboard.hpp:122
Definition blackboard_key_info_py.hpp:37
bool is_python_sequence_like(const py::handle &value)
Definition blackboard_key_info_py.hpp:90
bool is_python_number_like(const py::handle &value)
Definition blackboard_key_info_py.hpp:86
std::vector< std::string > StringVector
Definition blackboard_key_info_py.hpp:39
std::vector< bool > BoolVector
Definition blackboard_key_info_py.hpp:42
std::vector< T > sequence_to_vector(const py::sequence &seq)
Definition blackboard_key_info_py.hpp:105
bool dict_has_only_string_keys(const py::dict &dict)
Definition blackboard_key_info_py.hpp:116
bool sequence_matches(const py::sequence &seq, Predicate pred)
Definition blackboard_key_info_py.hpp:95
std::vector< double > FloatVector
Definition blackboard_key_info_py.hpp:41
bool is_python_float_like(const py::handle &value)
Definition blackboard_key_info_py.hpp:82
std::unordered_map< std::string, std::int64_t > IntDict
Definition blackboard_key_info_py.hpp:45
std::unordered_map< std::string, double > FloatDict
Definition blackboard_key_info_py.hpp:46
bool is_exact_cpp_type(const std::string &type)
Definition blackboard_key_info_py.hpp:148
std::vector< std::int64_t > IntVector
Definition blackboard_key_info_py.hpp:40
bool dict_values_match(const py::dict &dict, Predicate pred)
Definition blackboard_key_info_py.hpp:126
bool is_python_int_like(const py::handle &value)
Definition blackboard_key_info_py.hpp:78
bool is_python_bytes_like(const py::handle &value)
Definition blackboard_key_info_py.hpp:74
py::bytes byte_vector_to_py_bytes(const std::vector< ByteT > &value)
Definition blackboard_key_info_py.hpp:69
std::vector< uint8_t > py_buffer_to_vector(const py::handle &value)
Definition blackboard_key_info_py.hpp:49
std::unordered_map< std::string, T > dict_to_unordered_map(const py::dict &dict)
Definition blackboard_key_info_py.hpp:137
std::unordered_map< std::string, bool > BoolDict
Definition blackboard_key_info_py.hpp:47
std::unordered_map< std::string, std::string > StringDict
Definition blackboard_key_info_py.hpp:44
Definition blackboard.hpp:34
py::object blackboard_key_info_get_py_default(const BlackboardKeyInfo &info)
Convert a BlackboardKeyInfo default value back to a Python object.
Definition blackboard_key_info_py.hpp:284
std::string demangle_type(const std::string &mangled_name)
Demangle a C++ type name to a human-readable format.
Definition blackboard.hpp:41
BlackboardKeyInfo blackboard_key_info_from_pyobject(const std::string &key_name, py::object value)
Create a BlackboardKeyInfo from a Python object default value.
Definition blackboard_key_info_py.hpp:162
Extension methods for BlackboardKeyInfo. Added via ADL-friendly free functions used by the pybind cod...
Definition blackboard_key_info_py.hpp:408
static py::object get_py_default_value(const BlackboardKeyInfo &info)
Definition blackboard_key_info_py.hpp:414
static BlackboardKeyInfo from_pyobject(const std::string &key_name, py::object value)
Definition blackboard_key_info_py.hpp:409
Information about a blackboard key including name and optional default value.
Definition blackboard_key_info.hpp:35
bool has_default
Whether this key has a default value.
Definition blackboard_key_info.hpp:41
T get_default_value() const
Retrieve the default value as the given type.
Definition blackboard_key_info.hpp:113
std::function< void(Blackboard &, const std::string &)> inject_default
Function to inject the default value into a blackboard at the given key.
Definition blackboard_key_info.hpp:48
std::string default_value_type
The type name of the default value.
Definition blackboard_key_info.hpp:46
std::shared_ptr< void > default_value
Definition blackboard_key_info.hpp:44