Skip to content

Commit 59e69ef

Browse files
author
opennao
committed
Merge commit 'refs/integration/team/platform/merge-naoqi-libqi-301'
2 parents b1f80de + 2dd36b1 commit 59e69ef

7 files changed

Lines changed: 146 additions & 60 deletions

File tree

cmake/set_libqi_dependency.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
set(LIBQI_VERSION 1.8.3
1+
set(LIBQI_VERSION 1.8.6
22
CACHE STRING
33
"Version of LibQi to use. If not empty, it will be checked against the \
44
version in the `package.xml` file in the libqi sources.")

qi/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.0.0'
1+
__version__ = '2.0.1'

qipython/common.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ namespace detail
146146
[&] {
147147
currentObject =
148148
reinterpret_borrow<object>(src);
149-
return qi::py::unwrapAsRef(&currentObject);
149+
return qi::py::unwrapAsRef(currentObject);
150150
});
151151
return value.isValid();
152152
}

qipython/pytypes.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ pybind11::object unwrapValue(AnyReference val);
4949
/// introspected and a `qi::py::Object` is constructed out of it by executing
5050
/// `qi::py::toObject`.
5151
///
52-
/// @pre `obj != nullptr && *obj`
53-
AnyReference unwrapAsRef(pybind11::object* obj);
52+
/// @pre `obj`
53+
AnyReference unwrapAsRef(pybind11::object& obj);
5454

5555
void registerTypes();
5656

src/pyproperty.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ void exportProperty(::py::module& m)
133133
call_guard<gil_scoped_release>(), arg(asyncArgName) = false)
134134
.def("setValue",
135135
[](detail::ProxyProperty& prop, object pyValue, bool async) {
136-
AnyValue value(unwrapAsRef(&pyValue));
136+
AnyValue value(unwrapAsRef(pyValue));
137137
gil_scoped_release unlock;
138138
const auto fut =
139139
toFuture(prop.object.setProperty(prop.propertyId, std::move(value))

src/pytypes.cpp

Lines changed: 93 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,23 @@ T* instance(Args&&... args)
258258
return &it->second;
259259
}
260260

261-
using DanglingReferencesStorage =
261+
/// A 'disowned' reference would be a reference that is not expected to be automatically destroyed,
262+
/// meaning we would have to manually call destroy on it. Such a storage enables us to associate
263+
/// such references to a type-erased value so that we can later retrieve them and destroy them
264+
/// manually.
265+
using DisownedReferencesStorage =
262266
boost::synchronized_value<std::map<void*, std::vector<AnyReference>>>;
263267

264-
void storeDanglingReference(void* context, AnyReference ref) noexcept
268+
void storeDisownedReference(void* context, AnyReference ref) noexcept
265269
{
266-
auto* storage = instance<DanglingReferencesStorage>();
270+
auto* storage = instance<DisownedReferencesStorage>();
267271
auto syncStorage = storage->synchronize();
268272
(*syncStorage)[context].push_back(ref);
269273
}
270274

271-
std::vector<AnyReference> unstoreDanglingReferences(void* context) noexcept
275+
std::vector<AnyReference> unstoreDisownedReferences(void* context) noexcept
272276
{
273-
auto* storage = instance<DanglingReferencesStorage>();
277+
auto* storage = instance<DisownedReferencesStorage>();
274278
auto syncStorage = storage->synchronize();
275279
auto it = syncStorage->find(context);
276280
if (it == syncStorage->end())
@@ -282,14 +286,40 @@ std::vector<AnyReference> unstoreDanglingReferences(void* context) noexcept
282286
return res;
283287
}
284288

285-
std::size_t destroyDanglingReferences(void* context) noexcept
289+
std::size_t destroyDisownedReferences(void* context) noexcept
286290
{
287-
const auto refs = unstoreDanglingReferences(context);
291+
const auto refs = unstoreDisownedReferences(context);
288292
for (auto ref : refs)
289293
ref.destroy();
290294
return refs.size();
291295
}
292296

297+
/// Associates a value to a Python object, so that it shares its lifetime.
298+
template<typename T>
299+
AnyReference associateValueToObj(::py::object& obj, T value)
300+
{
301+
const auto res = AnyValue::from(std::move(value)).release();
302+
auto pybindObjPtr = &obj;
303+
storeDisownedReference(pybindObjPtr, res);
304+
305+
// Create a weak reference to the target Python object that will track its lifetime and execute a
306+
// callback once it is destroyed.
307+
//
308+
// The callback ensures that all the references associated to the target object are destroyed, so
309+
// that we can leak them here and still ensure that they will be properly destroyed once needed.
310+
//
311+
// The callback also takes a handle to the weakref Python object itself. We need to make sure that
312+
// this weakref object lives as long as the target Python object so that the callback stays
313+
// attached to the lifetime of the target object. To do that, we leak the weakref Python object
314+
// by releasing it, and we manually decrement the reference count in the callback.
315+
auto weakref = ::py::weakref(obj, ::py::cpp_function([=](::py::handle weakref) {
316+
destroyDisownedReferences(pybindObjPtr);
317+
weakref.dec_ref();
318+
}));
319+
weakref.release();
320+
return res;
321+
}
322+
293323
} // namespace
294324

295325
::py::object unwrapValue(AnyReference val)
@@ -337,7 +367,7 @@ class ObjectInterfaceBase : public Interface
337367

338368
void destroy(void* storage) override
339369
{
340-
destroyDanglingReferences(storage);
370+
destroyDisownedReferences(storage);
341371
::py::gil_scoped_acquire lock;
342372
delete asObjectPtr(&storage);
343373
}
@@ -363,7 +393,7 @@ class DynamicInterface : public ObjectInterfaceBase<Storage, qi::DynamicTypeInte
363393
{
364394
::py::gil_scoped_acquire lock;
365395
const auto obj = this->asObjectPtr(&storage);
366-
return unwrapAsRef(obj);
396+
return unwrapAsRef(*obj);
367397
}
368398

369399
void set(void** storage, AnyReference src) override
@@ -559,9 +589,9 @@ class ListInterface : public ObjectInterfaceBase<Storage, qi::ListTypeInterface>
559589
const ::py::object element = list[index];
560590

561591
auto ref = AnyReference::from(element).clone();
562-
// Store the dangling reference with the list as a context instead of the
592+
// Store the disowned reference with the list as a context instead of the
563593
// iterator because the reference might outlive the iterator.
564-
storeDanglingReference(listStorage, ref);
594+
storeDisownedReference(listStorage, ref);
565595
return ref;
566596
}
567597

@@ -578,7 +608,7 @@ class ListInterface : public ObjectInterfaceBase<Storage, qi::ListTypeInterface>
578608
void* clone(void* storage) override { return DefaultImpl::clone(storage); }
579609
void destroy(void* storage) override
580610
{
581-
destroyDanglingReferences(storage);
611+
destroyDisownedReferences(storage);
582612
return DefaultImpl::destroy(storage);
583613
}
584614
const TypeInfo& info() override { return DefaultImpl::info(); }
@@ -657,9 +687,9 @@ class DictInterface: public ObjectInterfaceBase<Storage, qi::MapTypeInterface>
657687
const auto element = ::py::reinterpret_borrow<::py::object>(it->second);
658688
const auto keyElementPair = std::make_pair(key, element);
659689
auto ref = AnyReference::from(keyElementPair).clone();
660-
// Store the dangling reference with the list as a context instead of the
690+
// Store the disowned reference with the list as a context instead of the
661691
// iterator because the reference might outlive the iterator.
662-
storeDanglingReference(dictStorage, ref);
692+
storeDisownedReference(dictStorage, ref);
663693
return ref;
664694
}
665695

@@ -676,7 +706,7 @@ class DictInterface: public ObjectInterfaceBase<Storage, qi::MapTypeInterface>
676706
void* clone(void* storage) override { return DefaultImpl::clone(storage); }
677707
void destroy(void* storage) override
678708
{
679-
destroyDanglingReferences(storage);
709+
destroyDisownedReferences(storage);
680710
return DefaultImpl::destroy(storage);
681711
}
682712

@@ -749,78 +779,87 @@ class DictInterface: public ObjectInterfaceBase<Storage, qi::MapTypeInterface>
749779
}
750780

751781
auto ref = AnyReference::from(value).clone();
752-
// Store the dangling reference with the list as a context instead of the
782+
// Store the disowned reference with the list as a context instead of the
753783
// iterator because the reference might outlive the iterator.
754-
storeDanglingReference(storage, ref);
784+
storeDisownedReference(storage, ref);
755785
return ref;
756786
}
757787
};
758788

759789
} // namespace types
760790

761-
AnyReference unwrapAsRef(pybind11::object* obj)
791+
AnyReference unwrapAsRef(pybind11::object& obj)
762792
{
763-
QI_ASSERT_NOT_NULL(obj);
764-
QI_ASSERT_TRUE(*obj);
793+
QI_ASSERT_TRUE(obj);
765794

766795
::py::gil_scoped_acquire lock;
767796

768-
if (obj->is_none())
797+
if (obj.is_none())
769798
// The "void" value in AnyValue has no storage, so we can just release it
770799
// without risk of a leak.
771800
return AnyValue::makeVoid().release();
772801

773802
if ( ::py::isinstance<::py::ellipsis>(*obj)
774-
|| PyComplex_CheckExact(obj->ptr())
803+
|| PyComplex_CheckExact(obj.ptr())
775804
|| ::py::isinstance<::py::memoryview>(*obj)
776805
|| ::py::isinstance<::py::slice>(*obj)
777806
|| ::py::isinstance<::py::module>(*obj))
778807
{
779808
throw std::runtime_error("The Python type " +
780-
std::string(::py::str(obj->get_type())) +
809+
std::string(::py::str(obj.get_type())) +
781810
" is not handled");
782811
}
783812

784-
if (PyLong_CheckExact(obj->ptr()))
785-
return AnyReference(instance<types::IntInterface<::py::object>>(), obj);
813+
// Pointer to the libpython C library python object.
814+
const auto pyObjPtr = obj.ptr();
815+
816+
// Pointer to the pybind C++ library python object.
817+
const auto pybindObjPtr = &obj;
818+
819+
if (PyLong_CheckExact(pyObjPtr))
820+
return AnyReference(instance<types::IntInterface<::py::object>>(), pybindObjPtr);
786821

787-
if (PyFloat_CheckExact(obj->ptr()))
788-
return AnyReference(instance<types::FloatInterface<::py::object>>(), obj);
822+
if (PyFloat_CheckExact(pyObjPtr))
823+
return AnyReference(instance<types::FloatInterface<::py::object>>(), pybindObjPtr);
789824

790-
if (PyBool_Check(obj->ptr()))
791-
return AnyReference(instance<types::BoolInterface<::py::object>>(), obj);
825+
if (PyBool_Check(pyObjPtr))
826+
return AnyReference(instance<types::BoolInterface<::py::object>>(), pybindObjPtr);
792827

793-
if (PyUnicode_CheckExact(obj->ptr()))
794-
return AnyReference(instance<types::StrInterface<::py::object>>(), obj);
828+
if (PyUnicode_CheckExact(pyObjPtr))
829+
return AnyReference(instance<types::StrInterface<::py::object>>(), pybindObjPtr);
795830

796-
if (PyBytes_CheckExact(obj->ptr()) || PyByteArray_CheckExact(obj->ptr()))
797-
return AnyReference(instance<types::StringBufferInterface<::py::object>>(), obj);
831+
if (PyBytes_CheckExact(pyObjPtr) || PyByteArray_CheckExact(pyObjPtr))
832+
return AnyReference(instance<types::StringBufferInterface<::py::object>>(), pybindObjPtr);
798833

799-
if (PyTuple_CheckExact(obj->ptr()))
834+
if (PyTuple_CheckExact(pyObjPtr))
800835
return AnyReference(instance<types::StructuredIterableInterface<::py::object>>(
801-
::py::tuple(*obj).size()),
802-
obj);
836+
::py::tuple(obj).size()),
837+
pybindObjPtr);
803838

804839
// Checks if it is AnySet, meaning it can be a set or a frozenset.
805-
if (PyAnySet_CheckExact(obj->ptr()))
840+
if (PyAnySet_CheckExact(pyObjPtr))
806841
return AnyReference(instance<types::StructuredIterableInterface<::py::object>>(
807-
::py::set(*obj).size()),
808-
obj);
809-
810-
if (PyList_CheckExact(obj->ptr()))
811-
return AnyReference(instance<types::ListInterface<::py::object, ::py::list>>(), obj);
812-
813-
if (PyDict_CheckExact(obj->ptr()))
814-
return AnyReference(instance<types::DictInterface<::py::object>>(), obj);
815-
816-
auto qiObj = py::toObject(*obj);
817-
const auto res = AnyValue::from(std::move(qiObj)).release();
818-
storeDanglingReference(obj, res);
819-
::py::weakref(*obj, ::py::cpp_function([=](::py::handle ref) {
820-
destroyDanglingReferences(obj);
821-
ref.dec_ref(); // release the reference to the weak reference itself.
822-
})).release(); // leak the weak reference, it will be released by the callback.
823-
return res;
842+
::py::set(obj).size()),
843+
pybindObjPtr);
844+
845+
if (PyList_CheckExact(pyObjPtr) || PyDictViewSet_Check(pyObjPtr) || PyDictValues_Check(pyObjPtr))
846+
return AnyReference(instance<types::ListInterface<::py::object, ::py::list>>(), pybindObjPtr);
847+
848+
if (PyDict_CheckExact(pyObjPtr))
849+
return AnyReference(instance<types::DictInterface<::py::object>>(), pybindObjPtr);
850+
851+
// At the moment in libqi, the `LogLevel` type is not registered in the qi type system. If we use
852+
// `AnyValue::from` with a `LogLevel` value, we get a value with a dummy type that cannot be set
853+
// or read. Furthermore, when registered with the `QI_TYPE_ENUM` macro, enumeration types are
854+
// treated as `int` values. This forces us to cast it here as an `int` to try to stay compatible.
855+
if (::py::isinstance<LogLevel>(obj))
856+
{
857+
const auto logLevel = obj.cast<LogLevel>();
858+
const auto logLevelValue = static_cast<int>(logLevel);
859+
return associateValueToObj(obj, logLevelValue);
860+
}
861+
862+
return associateValueToObj(obj, py::toObject(obj));
824863
}
825864

826865
void registerTypes()

tests/test_types.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,3 +401,50 @@ TEST_F(TypePassing, ReverseDict)
401401
};
402402
EXPECT_TRUE(getService().call<bool>("func", expected));
403403
}
404+
405+
TEST_F(TypePassing, LogLevel)
406+
{
407+
exec(
408+
"class TestService:\n"
409+
" def func(self):\n"
410+
" return qi.LogLevel.Info\n"
411+
);
412+
registerService();
413+
EXPECT_EQ(qi::LogLevel_Info, getService().call<int>("func"));
414+
}
415+
416+
TEST_F(TypePassing, DictViews)
417+
{
418+
exec(
419+
"class TestService:\n"
420+
" def func(self, kind):\n"
421+
" d = dict(one=1, two=2)\n"
422+
" if kind == 0:\n"
423+
" return d.keys()\n"
424+
" if kind == 1:\n"
425+
" return d.items()\n"
426+
" return d.values()\n"
427+
);
428+
registerService();
429+
430+
{ // Keys
431+
using Keys = std::set<std::string>;
432+
const Keys expected{ "one", "two" };
433+
EXPECT_EQ(expected, getService().call<Keys>("func", 0));
434+
}
435+
436+
{ // Items
437+
using Items = std::set<std::pair<std::string, int>>;
438+
const Items expected{ { "one", 1 }, { "two", 2 } };
439+
EXPECT_EQ(expected, getService().call<Items>("func", 1));
440+
}
441+
442+
443+
{ // Values
444+
using Values = std::vector<int>;
445+
const Values expected{ 1, 2 };
446+
auto values = getService().call<Values>("func", 2);
447+
std::sort(values.begin(), values.end());
448+
EXPECT_EQ(expected, values);
449+
}
450+
}

0 commit comments

Comments
 (0)