Skip to content

Commit

Permalink
Fix memory leaks, add docs, and address comments
Browse files Browse the repository at this point in the history
Signed-off-by: Arham Chopra <[email protected]>
  • Loading branch information
arhamchopra committed Jul 17, 2024
1 parent 3332483 commit a7db187
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 15 deletions.
34 changes: 20 additions & 14 deletions cpp/csp/python/PyStructToJson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,35 +198,37 @@ rapidjson::Value toJsonRecursive( const StructPtr& self, rapidjson::Document& do
return new_dict;
}

std::pair<const char *, Py_ssize_t> pyObjectToString( PyObject * py_obj )
{
auto * str_obj = PyObject_Str( py_obj );
Py_ssize_t len = 0;
const char * str = PyUnicode_AsUTF8AndSize( str_obj, &len );
return std::make_pair( str, len );
}

rapidjson::Value pyDictKeyToName( PyObject * py_key, rapidjson::Document& doc, PyObject * callable )
{
// NOTE: Only support None, bool, strings, ints, and floats, date, time, datetime, enums, csp.Enums as keys
// JSON encoding requires all names to be strings so convert them to strings

static thread_local PyTypeObjectPtr s_tl_enum_type;
// Get the enum type on the first call and save it for future use
if( s_tl_enum_type.get() == nullptr )
if( s_tl_enum_type.get() == nullptr ) [[unlikely]]
{
// Import enum module to extract the Enum type
auto py_enum_module = PyObjectPtr::own( PyImport_ImportModule( "enum" ) );
if( py_enum_module.get() )
{
s_tl_enum_type = PyTypeObjectPtr::own( reinterpret_cast<PyTypeObject*>( PyObject_GetAttrString( py_enum_module.get(), "Enum" ) ) );
}
else
{
CSP_THROW( RuntimeException, "Unable to import enum module from the python standard library" );
}
}

rapidjson::Value val;
if( ( py_key == Py_None ) || ( PyBool_Check( py_key ) ) )
if( py_key == Py_None )
{
val.SetString( "null" );
}
else if( PyBool_Check( py_key ) )
{
auto[str, len] = pyObjectToString( py_key );
auto str_obj = PyObjectPtr::own( PyObject_Str( py_key ) );
Py_ssize_t len = 0;
const char * str = PyUnicode_AsUTF8AndSize( str_obj.get(), &len );
val.SetString( str, len, doc.GetAllocator() );
}
else if( PyUnicode_Check( py_key ) )
Expand All @@ -246,7 +248,8 @@ rapidjson::Value pyDictKeyToName( PyObject * py_key, rapidjson::Document& doc, P
auto json_obj = doubleToJson( key, doc );
if ( json_obj.IsNull() )
{
auto[str, len] = pyObjectToString( py_key );
auto str_obj = PyObjectPtr::own( PyObject_Str( py_key ) );
const char * str = PyUnicode_AsUTF8( str_obj.get() );
CSP_THROW( ValueError, "Cannot serialize " + std::string( str ) + " to key in JSON" );
}
else
Expand Down Expand Up @@ -279,8 +282,11 @@ rapidjson::Value pyDictKeyToName( PyObject * py_key, rapidjson::Document& doc, P
}
else if( PyType_IsSubtype( Py_TYPE( py_key ), s_tl_enum_type.get() ) )
{
auto enum_name = PyObjectPtr::own( PyObject_GetAttrString( py_key, "name" ) );
auto[str, len] = pyObjectToString( enum_name.get() );
// Use the `name` attribute of the enum for the string representation
auto py_enum_name = PyObjectPtr::own( PyObject_GetAttrString( py_key, "name" ) );
auto str_obj = PyObjectPtr::own( PyObject_Str( py_enum_name.get() ) );
Py_ssize_t len = 0;
const char * str = PyUnicode_AsUTF8AndSize( str_obj.get(), &len );
val.SetString( str, len, doc.GetAllocator() );
}
else
Expand Down
2 changes: 1 addition & 1 deletion csp/tests/impl/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,7 @@ class MyStruct(csp.Struct):
d_none = {
None: 2,
}
d_none_res = {str(k): v for k, v in d_none.items()}
d_none_res = {"null": 2}
test_struct = MyStruct(i=456, d_any=d_none)
result_dict = {"i": 456, "d_any": d_none_res}
self.assertEqual(json.loads(test_struct.to_json()), result_dict)
Expand Down
1 change: 1 addition & 0 deletions docs/wiki/api-references/csp.Struct-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ print(f"Using FastList field: value {s.a}, type {type(s.a)}, is Python list: {is
- **`from_dict(self, dict)`**: convert a regular python dict to an instance of the struct
- **`metadata(self)`**: returns the struct's metadata as a dictionary of key : type pairs
- **`to_dict(self)`**: convert struct instance to a python dictionary
- **`to_json(self, callback=lamda x: x)`**: convert struct instance to a json string, callback is invoked for any values encountered when processing the struct that are not basic python types, datetime types, tuples, lists, dicts, csp.Structs, or csp.Enums. The callback should convert the unhandled type to a combination of the known types.
- **`all_fields_set(self)`**: returns `True` if all the fields on the struct are set. Note that this will not recursively check sub-struct fields

# Note on inheritance
Expand Down

0 comments on commit a7db187

Please sign in to comment.