Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cpp/csp/python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ add_library(csptypesimpl
PyCspType.cpp
PyStruct.cpp
PyStructToJson.cpp
PyStructToDict.cpp)
PyStructToDict.cpp
PyStructFromDict.cpp
PyStructFromJson.cpp)

set_target_properties(csptypesimpl PROPERTIES PUBLIC_HEADER "${CSPTYPESIMPL_PUBLIC_HEADERS}")
target_compile_definitions(csptypesimpl PUBLIC RAPIDJSON_HAS_STDSTRING=1)
Expand Down Expand Up @@ -48,7 +50,9 @@ set(CSPIMPL_PUBLIC_HEADERS
PyOutputProxy.h
PyConstants.h
PyStructToJson.h
PyStructToDict.h)
PyStructToDict.h
PyStructFromDict.h
PyStructFromJson.h)

add_library(cspimpl SHARED
cspimpl.cpp
Expand Down
39 changes: 39 additions & 0 deletions cpp/csp/python/PyStruct.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <csp/python/PyStructList_impl.h>
#include <csp/python/PyStructToJson.h>
#include <csp/python/PyStructToDict.h>
#include <csp/python/PyStructFromDict.h>
#include <csp/python/PyStructFromJson.h>
#include <rapidjson/error/en.h>
#include <unordered_set>
#include <type_traits>

Expand Down Expand Up @@ -1052,6 +1055,40 @@ PyObject * PyStruct_to_dict( PyStruct * self, PyObject * args, PyObject * kwargs
CSP_RETURN_NULL;
}

PyObject * PyStruct_from_dict( PyStructMeta * cls, PyObject * args, PyObject * kwargs) {
CSP_BEGIN_METHOD;

PyObject* dict = NULL;

if (!PyArg_ParseTuple(args, "O:from_dict", &dict)) {
return NULL;
}
auto& struct_meta = cls->structMeta;
return toPython(structFromDict(struct_meta, dict));

CSP_RETURN_NULL;
}

PyObject * PyStruct_from_json( PyStructMeta * cls, PyObject * args, PyObject * kwargs )
{
CSP_BEGIN_METHOD;

const char * json = nullptr;
Py_ssize_t len = 0;
if( !PyArg_ParseTuple( args, "s#:from_json", &json, &len ) )
return NULL;

//kParseNanAndInfFlag to match to_json, which writes NaN / Inf for doubles
rapidjson::Document doc;
rapidjson::ParseResult ok = doc.Parse<rapidjson::kParseNanAndInfFlag>( json, len );
if( !ok )
CSP_THROW( ValueError, "Failed to parse json: " << rapidjson::GetParseError_En( ok.Code() ) );

return toPython( structFromJson( cls -> structMeta, doc ) );

CSP_RETURN_NULL;
}

PyObject * PyStruct_to_json( PyStruct * self, PyObject * args, PyObject * kwargs )
{
CSP_BEGIN_METHOD;
Expand Down Expand Up @@ -1087,6 +1124,8 @@ static PyMethodDef PyStruct_methods[] = {
{ "all_fields_set", (PyCFunction) PyStruct_all_fields_set, METH_NOARGS, "return true if all fields on the struct are set" },
{ "to_dict", (PyCFunction) PyStruct_to_dict, METH_VARARGS | METH_KEYWORDS, "return a python dict of the struct by recursively converting struct members into python dicts" },
{ "to_json", (PyCFunction) PyStruct_to_json, METH_VARARGS | METH_KEYWORDS, "return a json string of the struct by recursively converting struct members into json format" },
{ "from_dict", (PyCFunction) PyStruct_from_dict, METH_VARARGS | METH_KEYWORDS | METH_CLASS, "return a struct by recursively reading the values from a python dictionary"},
{ "from_json", (PyCFunction) PyStruct_from_json, METH_VARARGS | METH_KEYWORDS | METH_CLASS, "return a struct by recursively reading the values from a json string"},
{ NULL}
};

Expand Down
90 changes: 90 additions & 0 deletions cpp/csp/python/PyStructFromDict.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include <csp/python/PyStructFromDict.h>

namespace csp::python{

StructPtr structFromDict (const StructMetaPtr& struct_meta, PyObject* dict) {
PyObject* py_key;
PyObject* py_value;
Py_ssize_t ppos = 0;

if (!PyDict_Check(dict)) {
CSP_THROW(TypeError, "Wrong type used in `from_dict`, expected a dict.");
}

StructPtr s = struct_meta->create();
while( PyDict_Next( dict, &ppos, &py_key, &py_value ) )
{
if( !PyUnicode_Check( py_key ) )
CSP_THROW( KeyError, "Unexpected key " << PyObjectPtr::incref( py_key )
<< " for type " << struct_meta -> name() );

auto & field = struct_meta -> field( PyUnicode_AsUTF8( py_key ) );
if( !field )
CSP_THROW( KeyError, "Unexpected key " << PyObjectPtr::incref( py_key )
<< " for type " << struct_meta -> name() );

switchCspType(field->type(), [&] (auto tag)
{
using CType = typename decltype( tag )::type;
auto * typedField = static_cast<const typename StructField::upcast<CType>::type *>( field.get() );

//optional fields accept None, same as PyStruct_setattrs
if( typedField -> isOptional() && py_value == Py_None )
{
typedField -> setNone( s.get() );
typedField -> clearValue( s.get() );
return;
}

if constexpr (std::is_same_v<CType, StructPtr>)
{
auto& nestedMeta = static_cast<const CspStructType &>(* field->type()).meta();
typedField -> setValue(s.get(), structFromDict(nestedMeta, py_value));
}
else if constexpr( std::is_same_v<CType, CspEnum> )
{
//enums arrive as name strings unless to_dict was called with preserve_enums
auto & enumMeta = static_cast<const CspEnumType &>( *field -> type() ).meta();
if( PyUnicode_Check( py_value ) )
typedField -> setValue( s.get(), enumMeta -> fromString( PyUnicode_AsUTF8( py_value ) ) );
else
typedField -> setValue( s.get(), fromPython<CType>( py_value, *field -> type() ) );
}
else if constexpr( std::is_same_v<CType, std::vector<CspEnum>> )
{
//convert element-wise, entries may be names or enum objects
auto & elemType = static_cast<const CspArrayType &>( *field -> type() ).elemType();
auto & enumMeta = static_cast<const CspEnumType &>( *elemType ).meta();

PyObjectPtr seq = PyObjectPtr::own( PySequence_Fast( py_value, "expected a sequence of enum values" ) );
if( !seq.ptr() )
CSP_THROW( PythonPassthrough, "" );

std::vector<CspEnum> out;
Py_ssize_t n = PySequence_Fast_GET_SIZE( seq.ptr() );
out.reserve( n );
for( Py_ssize_t i = 0; i < n; ++i )
{
PyObject * elem = PySequence_Fast_GET_ITEM( seq.ptr(), i );
out.push_back( PyUnicode_Check( elem ) ? enumMeta -> fromString( PyUnicode_AsUTF8( elem ) )
: fromPython<CspEnum>( elem, *elemType ) );
}
typedField -> setValue( s.get(), out );
}
else
{
typedField -> setValue(s.get(), fromPython<CType>(py_value, *field->type()));
}
});

}

//we bypass __init__ so run the strict-struct check ourselves
if( !s -> validate() ) [[unlikely]]
CSP_THROW( ValueError, "Struct " << struct_meta -> name() << " is not valid; required fields "
<< s -> formatAllUnsetStrictFields() << " were not set on init" );

return s;
}

}
11 changes: 11 additions & 0 deletions cpp/csp/python/PyStructFromDict.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#pragma once

#include <csp/python/Conversions.h>

namespace csp::python
{

// Build a csp struct from a python dictionary.
StructPtr structFromDict( const StructMetaPtr& struct_meta, PyObject* dict);

}
45 changes: 45 additions & 0 deletions cpp/csp/python/PyStructFromJson.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <csp/python/PyStructFromJson.h>


namespace csp::python {

StructPtr structFromJson( const StructMetaPtr& struct_meta, const rapidjson::Value& jValue )
{
StructPtr s = struct_meta->create();

if( !jValue.IsObject() ) {
CSP_THROW( TypeError, "Expected a json object for type " << struct_meta->name() );
}

// Iterate all the fields of a valid JSON object.
for (auto jit = jValue.MemberBegin(); jit != jValue.MemberEnd(); ++jit) {
auto& field = struct_meta->field(jit->name.GetString());
if (!field) {
CSP_THROW( KeyError, "Unexpected key " << jit -> name.GetString() << " for type "
<< struct_meta -> name() );
}
switchCspType(field->type(), [&](auto tag) {
using CType = typename decltype(tag)::type;
auto * typedField = static_cast<const typename StructField::upcast<CType>::type *>( field.get() );

//to_json writes null for fields that are set to None
if( typedField -> isOptional() && jit->value.IsNull() )
{
typedField -> setNone( s.get() );
typedField -> clearValue( s.get() );
return;
}

typedField -> setValue( s.get(), fromJson<CType>( jit->value, *field->type() ) );
}
);
}

if (!s->validate()) {
CSP_THROW(ValueError, "struct " << struct_meta->name() << " is not valid " << "required fields " <<
s->formatAllUnsetStrictFields() << " were not set on init");
}

return s;
}
}
Loading