[api] Add QgsSipUtils, with method to test if object is owned by Python

QgsSipUtils.isPyOwned will return True if an object is owned
by python, or False if ownership is held by another object
or c++ class.

This gives a way to test in advance if we can safely assign
an object to a method which takes ownership (which always results
in a crash).
This commit is contained in:
Nyall Dawson 2025-05-03 16:20:52 +10:00
parent 19f246ecb1
commit 72ad735b1e
10 changed files with 213 additions and 0 deletions

View File

@ -0,0 +1,5 @@
# The following has been generated automatically from src/core/qgssiputils.h
try:
QgsSipUtils.isPyOwned = staticmethod(QgsSipUtils.isPyOwned)
except (NameError, AttributeError):
pass

View File

@ -0,0 +1,50 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgssiputils.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsSipUtils
{
%Docstring(signature="appended")
Contains utilities for working with SIP Python objects.
.. versionadded:: 3.44
%End
%TypeHeaderCode
#include "qgssiputils.h"
%End
public:
static bool isPyOwned( SIP_PYOBJECT /GetWrapper/ );
%Docstring
Returns ``True`` if an object is currently owned by Python.
If ``False`` is returned, then the object is currently owned by another
object (e.g. a c++ class).
%End
%MethodCode
if ( sipIsOwnedByPython( ( sipSimpleWrapper * )a0Wrapper ) )
{
sipRes = true;
}
else
{
sipRes = false;
}
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgssiputils.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -170,6 +170,7 @@
%Include auto_generated/qgsselectioncontext.sip %Include auto_generated/qgsselectioncontext.sip
%Include auto_generated/qgssimplifymethod.sip %Include auto_generated/qgssimplifymethod.sip
%Include auto_generated/qgssingleitemmodel.sip %Include auto_generated/qgssingleitemmodel.sip
%Include auto_generated/qgssiputils.sip
%Include auto_generated/qgssldexportcontext.sip %Include auto_generated/qgssldexportcontext.sip
%Include auto_generated/qgssnappingconfig.sip %Include auto_generated/qgssnappingconfig.sip
%Include auto_generated/qgssnappingutils.sip %Include auto_generated/qgssnappingutils.sip

View File

@ -0,0 +1,5 @@
# The following has been generated automatically from src/core/qgssiputils.h
try:
QgsSipUtils.isPyOwned = staticmethod(QgsSipUtils.isPyOwned)
except (NameError, AttributeError):
pass

View File

@ -0,0 +1,50 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgssiputils.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/
class QgsSipUtils
{
%Docstring(signature="appended")
Contains utilities for working with SIP Python objects.
.. versionadded:: 3.44
%End
%TypeHeaderCode
#include "qgssiputils.h"
%End
public:
static bool isPyOwned( SIP_PYOBJECT /GetWrapper/ );
%Docstring
Returns ``True`` if an object is currently owned by Python.
If ``False`` is returned, then the object is currently owned by another
object (e.g. a c++ class).
%End
%MethodCode
if ( sipIsOwnedByPython( ( sipSimpleWrapper * )a0Wrapper ) )
{
sipRes = true;
}
else
{
sipRes = false;
}
%End
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgssiputils.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.py again *
************************************************************************/

View File

@ -170,6 +170,7 @@
%Include auto_generated/qgsselectioncontext.sip %Include auto_generated/qgsselectioncontext.sip
%Include auto_generated/qgssimplifymethod.sip %Include auto_generated/qgssimplifymethod.sip
%Include auto_generated/qgssingleitemmodel.sip %Include auto_generated/qgssingleitemmodel.sip
%Include auto_generated/qgssiputils.sip
%Include auto_generated/qgssldexportcontext.sip %Include auto_generated/qgssldexportcontext.sip
%Include auto_generated/qgssnappingconfig.sip %Include auto_generated/qgssnappingconfig.sip
%Include auto_generated/qgssnappingutils.sip %Include auto_generated/qgssnappingutils.sip

View File

@ -1293,6 +1293,7 @@ set(QGIS_CORE_HDRS
qgsshapegenerator.h qgsshapegenerator.h
qgssimplifymethod.h qgssimplifymethod.h
qgssingleitemmodel.h qgssingleitemmodel.h
qgssiputils.h
qgssldexportcontext.h qgssldexportcontext.h
qgssnappingconfig.h qgssnappingconfig.h
qgssnappingutils.h qgssnappingutils.h

56
src/core/qgssiputils.h Normal file
View File

@ -0,0 +1,56 @@
/***************************************************************************
qgssiputils.h
-------------------
begin : May 2025
copyright : (C) 2025 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSSIPUTILS_H
#define QGSSIPUTILS_H
#include "qgis_core.h"
#include "qgis_sip.h"
/**
* \brief Contains utilities for working with SIP Python objects.
*
* \ingroup core
* \since QGIS 3.44
*/
class CORE_EXPORT QgsSipUtils
{
public:
#ifdef SIP_RUN
/**
* Returns TRUE if an object is currently owned by Python.
*
* If FALSE is returned, then the object is currently owned by another
* object (e.g. a c++ class).
*/
static bool isPyOwned( SIP_PYOBJECT SIP_GETWRAPPER );
% MethodCode
if ( sipIsOwnedByPython( ( sipSimpleWrapper * )a0Wrapper ) )
{
sipRes = true;
}
else
{
sipRes = false;
}
% End
#endif
};
#endif //QGSSIPUTILS_H

View File

@ -318,6 +318,7 @@ ADD_PYTHON_TEST(PyQgsSingleBandPseudoColorRenderer test_qgssinglebandpseudocolor
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py) ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
ADD_PYTHON_TEST(PyQgsRasterSingleColorRenderer test_qgsrastersinglecolorrenderer.py) ADD_PYTHON_TEST(PyQgsRasterSingleColorRenderer test_qgsrastersinglecolorrenderer.py)
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py) ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
ADD_PYTHON_TEST(PyQgsSipUtils test_qgssiputils.py)
ADD_PYTHON_TEST(PyQgsSphere test_qgssphere.py) ADD_PYTHON_TEST(PyQgsSphere test_qgssphere.py)
ADD_PYTHON_TEST(PyQgsSvgCache test_qgssvgcache.py) ADD_PYTHON_TEST(PyQgsSvgCache test_qgssvgcache.py)
ADD_PYTHON_TEST(PyQgsSymbolLayerRegistry test_qgssymbollayerregistry.py) ADD_PYTHON_TEST(PyQgsSymbolLayerRegistry test_qgssymbollayerregistry.py)

View File

@ -0,0 +1,43 @@
"""QGIS Unit tests for QgsSipUtils
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
from qgis.core import (
QgsSipUtils,
QgsGeometry,
QgsSingleSymbolRenderer,
QgsPoint,
QgsFillSymbol,
)
import unittest
from qgis.testing import QgisTestCase
class TestQgsSipUtils(QgisTestCase):
def test_isPyOwned(self):
# not a sip object
self.assertTrue(QgsSipUtils.isPyOwned(5))
p = QgsPoint()
self.assertTrue(QgsSipUtils.isPyOwned(p))
# assign ownership to other object
g = QgsGeometry(p)
self.assertTrue(QgsSipUtils.isPyOwned(g))
self.assertFalse(QgsSipUtils.isPyOwned(p))
self.assertFalse(QgsSipUtils.isPyOwned(g.get()))
self.assertTrue(QgsSipUtils.isPyOwned(g.get().clone()))
renderer = QgsSingleSymbolRenderer(QgsFillSymbol.createSimple({}))
self.assertFalse(QgsSipUtils.isPyOwned(renderer.symbol()))
renderer2 = renderer.clone()
# an object created in c++, never seen before by sip/python
self.assertFalse(QgsSipUtils.isPyOwned(renderer2.symbol()))
if __name__ == "__main__":
unittest.main()