diff --git a/conandata.yml b/conandata.yml index 180b137..6ea3484 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,3 +1,3 @@ -version: "5.11.2-alpha.0" +version: "5.12.0" requirements: - "arcus/5.11.1" diff --git a/include/pyArcus/PythonMessage.h b/include/pyArcus/PythonMessage.h index 2e730f5..bc51ebb 100644 --- a/include/pyArcus/PythonMessage.h +++ b/include/pyArcus/PythonMessage.h @@ -6,6 +6,7 @@ #include "Arcus/Types.h" #include +#include namespace google { @@ -13,6 +14,7 @@ namespace protobuf { class Descriptor; class Reflection; +class FieldDescriptor; } // namespace protobuf } // namespace google @@ -60,7 +62,7 @@ class PythonMessage /** * Get the number of messages in a repeated message field. */ - int repeatedMessageCount(const std::string& field_name) const; + [[nodiscard]] int repeatedMessageCount(const std::string& field_name) const; /** * Get a specific instance of a message in a repeated message field. @@ -88,12 +90,36 @@ class PythonMessage * * \return The integer value of the specified enum. */ - int getEnumValue(const std::string& enum_value) const; + [[nodiscard]] int getEnumValue(const std::string& enum_value) const; /** * Internal. */ - MessagePtr getSharedMessage() const; + [[nodiscard]] MessagePtr getSharedMessage() const; + +private: + /*! + * Get the python object corresponding to the value of the given field + * @param field The field which value is to be retrieved + * @param index The index of the element if the field is repeatable, otherwise nullopt + * @return The corresponding python object, or nullptr if an error occured + */ + PyObject* getFieldValue(const google::protobuf::FieldDescriptor* field, const std::optional& index = std::nullopt) const; + + /*! + * Sets the value of the field given the corresponding python object + * @param field The field which value is to be set + * @param value The value to be set, as a raw python object + * @param append Whether the value should be appended to a repeatable field, or just set as a scalar value + */ + void setFieldValue(const google::protobuf::FieldDescriptor* field, PyObject* value, bool append); + + /*! + * @brief Replaces `_descriptor->FindFieldByName(field_name)` since it sometimes doesn't give an actual existing field + * @param field_name The name of the fields to be used + * @return The matching field descriptor, or nullptr if it was not found + */ + [[nodiscard]] const google::protobuf::FieldDescriptor* findFieldByNameHack(const std::string& field_name) const; private: MessagePtr _shared_message; diff --git a/src/PythonMessage.cpp b/src/PythonMessage.cpp index 2e9e52f..b7d0bda 100644 --- a/src/PythonMessage.cpp +++ b/src/PythonMessage.cpp @@ -5,48 +5,41 @@ #include +#include + #include -#include -using namespace Arcus; -using namespace google::protobuf; +namespace gp = google::protobuf; -PythonMessage::PythonMessage(google::protobuf::Message* message) +namespace Arcus { - _message = message; - _reflection = message->GetReflection(); - _descriptor = message->GetDescriptor(); -} -Arcus::PythonMessage::PythonMessage(const MessagePtr& message) +PythonMessage::PythonMessage(gp::Message* message) : _message(message), _reflection(message->GetReflection()), _descriptor(message->GetDescriptor()) { - _shared_message = message; - _message = message.get(); - _reflection = message->GetReflection(); - _descriptor = message->GetDescriptor(); } -PythonMessage::~PythonMessage() +PythonMessage::PythonMessage(const MessagePtr& message) : _shared_message(message), _message(message.get()), _reflection(message->GetReflection()), _descriptor(message->GetDescriptor()) { } -std::string Arcus::PythonMessage::getTypeName() const +PythonMessage::~PythonMessage() = default; + +std::string PythonMessage::getTypeName() const { return std::string(_message->GetTypeName()); } -MessagePtr Arcus::PythonMessage::getSharedMessage() const +MessagePtr PythonMessage::getSharedMessage() const { return _shared_message; } -// Instead of `_descriptor->FindFieldByName(field_name)`. -const google::protobuf::FieldDescriptor* findFieldByNameHack(const google::protobuf::Descriptor* _descriptor, const std::string_view field_name) +const gp::FieldDescriptor* PythonMessage::findFieldByNameHack(const std::string& field_name) const { for (int ii = 0; ii < _descriptor->field_count(); ++ii) { - auto candidate = _descriptor->field(ii); - if (field_name.compare(candidate->name()) == 0) + const gp::FieldDescriptor* candidate = _descriptor->field(ii); + if (field_name == candidate->name()) { return candidate; } @@ -54,132 +47,173 @@ const google::protobuf::FieldDescriptor* findFieldByNameHack(const google::proto return nullptr; } -bool Arcus::PythonMessage::__hasattr__(const std::string& field_name) const +bool PythonMessage::__hasattr__(const std::string& field_name) const { - auto field = findFieldByNameHack(_descriptor, field_name); + const gp::FieldDescriptor* field = findFieldByNameHack(field_name); return bool(field); } -PyObject* Arcus::PythonMessage::__getattr__(const std::string& field_name) const +PyObject* PythonMessage::getFieldValue(const gp::FieldDescriptor* field, const std::optional& index) const { - auto field = findFieldByNameHack(_descriptor, field_name); - if (! field) - { - PyErr_SetString(PyExc_AttributeError, field_name.c_str()); - return nullptr; - } - + const bool repeated = index.has_value(); switch (field->type()) { - case FieldDescriptor::TYPE_FLOAT: - return PyFloat_FromDouble(_reflection->GetFloat(*_message, field)); - case FieldDescriptor::TYPE_DOUBLE: - return PyFloat_FromDouble(_reflection->GetDouble(*_message, field)); - case FieldDescriptor::TYPE_INT32: - case FieldDescriptor::TYPE_FIXED32: - case FieldDescriptor::TYPE_SINT32: - case FieldDescriptor::TYPE_SFIXED32: - return PyLong_FromLong(_reflection->GetInt32(*_message, field)); - case FieldDescriptor::TYPE_INT64: - case FieldDescriptor::TYPE_FIXED64: - case FieldDescriptor::TYPE_SINT64: - case FieldDescriptor::TYPE_SFIXED64: - return PyLong_FromLongLong(_reflection->GetInt64(*_message, field)); - case FieldDescriptor::TYPE_UINT32: - return PyLong_FromUnsignedLong(_reflection->GetUInt32(*_message, field)); - case FieldDescriptor::TYPE_UINT64: - return PyLong_FromUnsignedLongLong(_reflection->GetUInt64(*_message, field)); - case FieldDescriptor::TYPE_BOOL: - if (_reflection->GetBool(*_message, field)) + case gp::FieldDescriptor::TYPE_FLOAT: + return PyFloat_FromDouble(repeated ? _reflection->GetRepeatedFloat(*_message, field, *index) : _reflection->GetFloat(*_message, field)); + case gp::FieldDescriptor::TYPE_DOUBLE: + return PyFloat_FromDouble(repeated ? _reflection->GetRepeatedDouble(*_message, field, *index) : _reflection->GetDouble(*_message, field)); + case gp::FieldDescriptor::TYPE_INT32: + case gp::FieldDescriptor::TYPE_FIXED32: + case gp::FieldDescriptor::TYPE_SINT32: + case gp::FieldDescriptor::TYPE_SFIXED32: + return PyLong_FromLong(repeated ? _reflection->GetRepeatedInt32(*_message, field, *index) : _reflection->GetInt32(*_message, field)); + case gp::FieldDescriptor::TYPE_INT64: + case gp::FieldDescriptor::TYPE_FIXED64: + case gp::FieldDescriptor::TYPE_SINT64: + case gp::FieldDescriptor::TYPE_SFIXED64: + return PyLong_FromLongLong(repeated ? _reflection->GetRepeatedInt64(*_message, field, *index) : _reflection->GetInt64(*_message, field)); + case gp::FieldDescriptor::TYPE_UINT32: + return PyLong_FromUnsignedLong(repeated ? _reflection->GetRepeatedUInt32(*_message, field, *index) : _reflection->GetUInt32(*_message, field)); + case gp::FieldDescriptor::TYPE_UINT64: + return PyLong_FromUnsignedLongLong(repeated ? _reflection->GetRepeatedUInt64(*_message, field, *index) : _reflection->GetUInt64(*_message, field)); + case gp::FieldDescriptor::TYPE_BOOL: + { + const bool value_bool = repeated ? _reflection->GetRepeatedBool(*_message, field, *index) : _reflection->GetBool(*_message, field); + if (value_bool) { Py_RETURN_TRUE; } - else - { - Py_RETURN_FALSE; - } - case FieldDescriptor::TYPE_BYTES: + Py_RETURN_FALSE; + } + case gp::FieldDescriptor::TYPE_BYTES: { - std::string data = _reflection->GetString(*_message, field); + const std::string data = repeated ? _reflection->GetRepeatedString(*_message, field, *index) : _reflection->GetString(*_message, field); return PyBytes_FromStringAndSize(data.c_str(), data.size()); } - case FieldDescriptor::TYPE_STRING: - return PyUnicode_FromString(_reflection->GetString(*_message, field).c_str()); - case FieldDescriptor::TYPE_ENUM: - return PyLong_FromLong(_reflection->GetEnumValue(*_message, field)); + case gp::FieldDescriptor::TYPE_STRING: + { + const std::string data = repeated ? _reflection->GetRepeatedString(*_message, field, *index) : _reflection->GetString(*_message, field); + return PyUnicode_FromString(data.c_str()); + } + case gp::FieldDescriptor::TYPE_ENUM: + return PyLong_FromLong(repeated ? _reflection->GetRepeatedEnumValue(*_message, field, *index) : _reflection->GetEnumValue(*_message, field)); default: PyErr_SetString(PyExc_ValueError, "Could not handle value of field"); return nullptr; } } -void Arcus::PythonMessage::__setattr__(const std::string& field_name, PyObject* value) +PyObject* PythonMessage::__getattr__(const std::string& field_name) const { - auto field = findFieldByNameHack(_descriptor, field_name); + const gp::FieldDescriptor* field = findFieldByNameHack(field_name); if (! field) { PyErr_SetString(PyExc_AttributeError, field_name.c_str()); - return; + return nullptr; } + if (field->is_repeated()) + { + const int count = _reflection->FieldSize(*_message, field); + PyObject* list = PyList_New(count); + if (! list) + { + return nullptr; + } + for (int i = 0; i < count; ++i) + { + PyObject* item = getFieldValue(field, i); + if (! item) + { + Py_DECREF(list); + return nullptr; + } + PyList_SET_ITEM(list, i, item); // steals reference + } + return list; + } + + return getFieldValue(field); +} + +void PythonMessage::setFieldValue(const gp::FieldDescriptor* field, PyObject* value, bool append) +{ switch (field->type()) { - case FieldDescriptor::TYPE_FLOAT: - _reflection->SetFloat(_message, field, PyFloat_AsDouble(value)); + case gp::FieldDescriptor::TYPE_FLOAT: + { + const double value_double = PyFloat_AsDouble(value); + append ? _reflection->AddFloat(_message, field, static_cast(value_double)) : _reflection->SetFloat(_message, field, static_cast(value_double)); break; - case FieldDescriptor::TYPE_DOUBLE: - _reflection->SetDouble(_message, field, PyFloat_AsDouble(value)); + } + case gp::FieldDescriptor::TYPE_DOUBLE: + { + const double value_double = PyFloat_AsDouble(value); + append ? _reflection->AddDouble(_message, field, value_double) : _reflection->SetDouble(_message, field, value_double); break; - case FieldDescriptor::TYPE_INT32: - case FieldDescriptor::TYPE_SFIXED32: - case FieldDescriptor::TYPE_FIXED32: - case FieldDescriptor::TYPE_SINT32: - _reflection->SetInt32(_message, field, PyLong_AsLong(value)); + } + case gp::FieldDescriptor::TYPE_INT32: + case gp::FieldDescriptor::TYPE_FIXED32: + case gp::FieldDescriptor::TYPE_SINT32: + case gp::FieldDescriptor::TYPE_SFIXED32: + { + const long value_long = PyLong_AsLong(value); + append ? _reflection->AddInt32(_message, field, static_cast(value_long)) : _reflection->SetInt32(_message, field, static_cast(value_long)); break; - case FieldDescriptor::TYPE_INT64: - case FieldDescriptor::TYPE_FIXED64: - case FieldDescriptor::TYPE_SINT64: - case FieldDescriptor::TYPE_SFIXED64: - _reflection->SetInt64(_message, field, PyLong_AsLongLong(value)); + } + case gp::FieldDescriptor::TYPE_INT64: + case gp::FieldDescriptor::TYPE_FIXED64: + case gp::FieldDescriptor::TYPE_SINT64: + case gp::FieldDescriptor::TYPE_SFIXED64: + { + const long long value_ll = PyLong_AsLongLong(value); + append ? _reflection->AddInt64(_message, field, static_cast(value_ll)) : _reflection->SetInt64(_message, field, static_cast(value_ll)); break; - case FieldDescriptor::TYPE_UINT32: - _reflection->SetUInt32(_message, field, PyLong_AsUnsignedLong(value)); + } + case gp::FieldDescriptor::TYPE_UINT32: + { + const unsigned long value_ul = PyLong_AsUnsignedLong(value); + append ? _reflection->AddUInt32(_message, field, static_cast(value_ul)) : _reflection->SetUInt32(_message, field, static_cast(value_ul)); break; - case FieldDescriptor::TYPE_UINT64: - _reflection->SetUInt64(_message, field, PyLong_AsUnsignedLongLong(value)); + } + case gp::FieldDescriptor::TYPE_UINT64: + { + const unsigned long long value_ull = PyLong_AsUnsignedLongLong(value); + append ? _reflection->AddUInt64(_message, field, static_cast(value_ull)) : _reflection->SetUInt64(_message, field, static_cast(value_ull)); break; - case FieldDescriptor::TYPE_BOOL: - if (value == Py_True) - { - _reflection->SetBool(_message, field, true); - } - else - { - _reflection->SetBool(_message, field, false); - } + } + case gp::FieldDescriptor::TYPE_BOOL: + { + const bool value_bool = value == Py_True; + append ? _reflection->AddBool(_message, field, value_bool) : _reflection->SetBool(_message, field, value_bool); break; - case FieldDescriptor::TYPE_BYTES: + } + case gp::FieldDescriptor::TYPE_BYTES: { Py_buffer buffer; PyObject_GetBuffer(value, &buffer, PyBUF_SIMPLE); - std::string str(reinterpret_cast(buffer.buf), buffer.len); - _reflection->SetString(_message, field, str); + std::string const str(reinterpret_cast(buffer.buf), buffer.len); + append ? _reflection->AddString(_message, field, str) : _reflection->SetString(_message, field, str); break; } - case FieldDescriptor::TYPE_STRING: - _reflection->SetString(_message, field, PyUnicode_AsUTF8(value)); + case gp::FieldDescriptor::TYPE_STRING: + { + const char* str = PyUnicode_AsUTF8(value); + append ? _reflection->AddString(_message, field, str) : _reflection->SetString(_message, field, str); break; - case FieldDescriptor::TYPE_ENUM: + } + case gp::FieldDescriptor::TYPE_ENUM: { if (PyUnicode_Check(value)) { - auto enum_value = _descriptor->FindEnumValueByName(PyUnicode_AsUTF8(value)); - _reflection->SetEnum(_message, field, enum_value); + const auto enum_value = _descriptor->FindEnumValueByName(PyUnicode_AsUTF8(value)); + append ? _reflection->AddEnum(_message, field, enum_value) : _reflection->SetEnum(_message, field, enum_value); } else { - _reflection->SetEnumValue(_message, field, PyLong_AsLong(value)); + const int value_int = static_cast(PyLong_AsLong(value)); + append ? _reflection->AddEnumValue(_message, field, value_int) : _reflection->SetEnumValue(_message, field, value_int); } break; } @@ -189,22 +223,56 @@ void Arcus::PythonMessage::__setattr__(const std::string& field_name, PyObject* } } -PythonMessage* Arcus::PythonMessage::addRepeatedMessage(const std::string& field_name) +void PythonMessage::__setattr__(const std::string& field_name, PyObject* value) +{ + auto field = findFieldByNameHack(field_name); + if (! field) + { + PyErr_SetString(PyExc_AttributeError, field_name.c_str()); + return; + } + + if (field->is_repeated()) + { + PyObject* iter = PyObject_GetIter(value); + if (! iter) + { + return; + } + _reflection->ClearField(_message, field); + PyObject* item; + while ((item = PyIter_Next(iter)) != nullptr) + { + setFieldValue(field, item, true); + Py_DECREF(item); + } + Py_DECREF(iter); + if (PyErr_Occurred()) + { + return; + } + return; + } + + setFieldValue(field, value, false); +} + +PythonMessage* PythonMessage::addRepeatedMessage(const std::string& field_name) { - auto field = findFieldByNameHack(_descriptor, field_name); + auto field = findFieldByNameHack(field_name); if (! field) { PyErr_SetString(PyExc_AttributeError, field_name.c_str()); return nullptr; } - Message* message = _reflection->AddMessage(_message, field); + gp::Message* message = _reflection->AddMessage(_message, field); return new PythonMessage(message); } int PythonMessage::repeatedMessageCount(const std::string& field_name) const { - auto field = findFieldByNameHack(_descriptor, field_name); + auto field = findFieldByNameHack(field_name); if (! field) { PyErr_SetString(PyExc_AttributeError, field_name.c_str()); @@ -214,9 +282,9 @@ int PythonMessage::repeatedMessageCount(const std::string& field_name) const return _reflection->FieldSize(*_message, field); } -PythonMessage* Arcus::PythonMessage::getMessage(const std::string& field_name) +PythonMessage* PythonMessage::getMessage(const std::string& field_name) { - auto field = findFieldByNameHack(_descriptor, field_name); + auto field = findFieldByNameHack(field_name); if (! field) { PyErr_SetString(PyExc_AttributeError, field_name.c_str()); @@ -225,9 +293,9 @@ PythonMessage* Arcus::PythonMessage::getMessage(const std::string& field_name) return new PythonMessage(_reflection->MutableMessage(_message, field)); } -PythonMessage* Arcus::PythonMessage::getRepeatedMessage(const std::string& field_name, int index) +PythonMessage* PythonMessage::getRepeatedMessage(const std::string& field_name, int index) { - auto field = findFieldByNameHack(_descriptor, field_name); + auto field = findFieldByNameHack(field_name); if (! field) { PyErr_SetString(PyExc_AttributeError, field_name.c_str()); @@ -243,7 +311,7 @@ PythonMessage* Arcus::PythonMessage::getRepeatedMessage(const std::string& field return new PythonMessage(_reflection->MutableRepeatedMessage(_message, field, index)); } -int Arcus::PythonMessage::getEnumValue(const std::string& enum_value) const +int PythonMessage::getEnumValue(const std::string& enum_value) const { auto field = _descriptor->FindEnumValueByName(enum_value); if (! field) @@ -253,3 +321,5 @@ int Arcus::PythonMessage::getEnumValue(const std::string& enum_value) const return field->number(); } + +} // namespace Arcus \ No newline at end of file