|
| 1 | +#include <csp/python/PyStructFromDict.h> |
| 2 | + |
| 3 | +namespace csp::python{ |
| 4 | + |
| 5 | +StructPtr structFromDict (const StructMetaPtr& struct_meta, PyObject* dict) { |
| 6 | + PyObject* py_key; |
| 7 | + PyObject* py_value; |
| 8 | + Py_ssize_t ppos = 0; |
| 9 | + |
| 10 | + if (!PyDict_Check(dict)) { |
| 11 | + CSP_THROW(TypeError, "Wrong type used in `from_dict`, expected a dict."); |
| 12 | + } |
| 13 | + |
| 14 | + StructPtr s = struct_meta->create(); |
| 15 | + while( PyDict_Next( dict, &ppos, &py_key, &py_value ) ) |
| 16 | + { |
| 17 | + if( !PyUnicode_Check( py_key ) ) |
| 18 | + CSP_THROW( KeyError, "Unexpected key " << PyObjectPtr::incref( py_key ) |
| 19 | + << " for type " << struct_meta -> name() ); |
| 20 | + |
| 21 | + auto & field = struct_meta -> field( PyUnicode_AsUTF8( py_key ) ); |
| 22 | + if( !field ) |
| 23 | + CSP_THROW( KeyError, "Unexpected key " << PyObjectPtr::incref( py_key ) |
| 24 | + << " for type " << struct_meta -> name() ); |
| 25 | + |
| 26 | + switchCspType(field->type(), [&] (auto tag) |
| 27 | + { |
| 28 | + using CType = typename decltype( tag )::type; |
| 29 | + auto * typedField = static_cast<const typename StructField::upcast<CType>::type *>( field.get() ); |
| 30 | + |
| 31 | + //optional fields accept None, same as PyStruct_setattrs |
| 32 | + if( typedField -> isOptional() && py_value == Py_None ) |
| 33 | + { |
| 34 | + typedField -> setNone( s.get() ); |
| 35 | + typedField -> clearValue( s.get() ); |
| 36 | + return; |
| 37 | + } |
| 38 | + |
| 39 | + if constexpr (std::is_same_v<CType, StructPtr>) |
| 40 | + { |
| 41 | + auto& nestedMeta = static_cast<const CspStructType &>(* field->type()).meta(); |
| 42 | + typedField -> setValue(s.get(), structFromDict(nestedMeta, py_value)); |
| 43 | + } |
| 44 | + else if constexpr( std::is_same_v<CType, CspEnum> ) |
| 45 | + { |
| 46 | + //enums arrive as name strings unless to_dict was called with preserve_enums |
| 47 | + auto & enumMeta = static_cast<const CspEnumType &>( *field -> type() ).meta(); |
| 48 | + if( PyUnicode_Check( py_value ) ) |
| 49 | + typedField -> setValue( s.get(), enumMeta -> fromString( PyUnicode_AsUTF8( py_value ) ) ); |
| 50 | + else |
| 51 | + typedField -> setValue( s.get(), fromPython<CType>( py_value, *field -> type() ) ); |
| 52 | + } |
| 53 | + else if constexpr( std::is_same_v<CType, std::vector<CspEnum>> ) |
| 54 | + { |
| 55 | + //convert element-wise, entries may be names or enum objects |
| 56 | + auto & elemType = static_cast<const CspArrayType &>( *field -> type() ).elemType(); |
| 57 | + auto & enumMeta = static_cast<const CspEnumType &>( *elemType ).meta(); |
| 58 | + |
| 59 | + PyObjectPtr seq = PyObjectPtr::own( PySequence_Fast( py_value, "expected a sequence of enum values" ) ); |
| 60 | + if( !seq.ptr() ) |
| 61 | + CSP_THROW( PythonPassthrough, "" ); |
| 62 | + |
| 63 | + std::vector<CspEnum> out; |
| 64 | + Py_ssize_t n = PySequence_Fast_GET_SIZE( seq.ptr() ); |
| 65 | + out.reserve( n ); |
| 66 | + for( Py_ssize_t i = 0; i < n; ++i ) |
| 67 | + { |
| 68 | + PyObject * elem = PySequence_Fast_GET_ITEM( seq.ptr(), i ); |
| 69 | + out.push_back( PyUnicode_Check( elem ) ? enumMeta -> fromString( PyUnicode_AsUTF8( elem ) ) |
| 70 | + : fromPython<CspEnum>( elem, *elemType ) ); |
| 71 | + } |
| 72 | + typedField -> setValue( s.get(), out ); |
| 73 | + } |
| 74 | + else |
| 75 | + { |
| 76 | + typedField -> setValue(s.get(), fromPython<CType>(py_value, *field->type())); |
| 77 | + } |
| 78 | + }); |
| 79 | + |
| 80 | + } |
| 81 | + |
| 82 | + //we bypass __init__ so run the strict-struct check ourselves |
| 83 | + if( !s -> validate() ) [[unlikely]] |
| 84 | + CSP_THROW( ValueError, "Struct " << struct_meta -> name() << " is not valid; required fields " |
| 85 | + << s -> formatAllUnsetStrictFields() << " were not set on init" ); |
| 86 | + |
| 87 | + return s; |
| 88 | +} |
| 89 | + |
| 90 | +} |
0 commit comments