tdf#171122 pyuno: Handle service constructors

This makes it so that services can be imported in Python with code like
this:

from com.sun.star.resource import StringResourceWithLocation

And then an instance of the service can be constructed using any of its
constructors as a class method like this:

res = StringResourceWithLocation.create(ctx, root_url, True, locale,
                                        dlg, '', None)

This has the advantage that the number of arguments can be checked
properly and a more useful error message can be reported if they are not
correct. Because pyuno knows what types the arguments are supposed to
be, it can also do a better job of coercing them to the right ones. For
example in the example above it is possible to pass None for an
interface argument even though that normally represents the void type.

This idea evolved out of the discussion for tdf#169229.

Change-Id: I9b542178f1c101d58dcbbd241d4ecbc9fe38bc2c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199104
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <stephan.bergmann@collabora.com>
This commit is contained in:
Neil Roberts
2026-03-04 13:52:55 +01:00
committed by Stephan Bergmann
parent 112e72dea2
commit 346e0f8891
5 changed files with 382 additions and 17 deletions
+1
View File
@@ -46,6 +46,7 @@ $(eval $(call gb_Library_add_exception_objects,pyuno,\
pyuno/source/module/pyuno_adapter \
pyuno/source/module/pyuno_gc \
pyuno/source/module/pyuno_iterator \
pyuno/source/module/pyuno_service_constructor \
))
# vim:set noet sw=4 ts=4:
+84 -16
View File
@@ -19,6 +19,8 @@
#include "pyuno_impl.hxx"
#include <typelib/typedescription.hxx>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
#include <com/sun/star/script/CannotConvertException.hpp>
@@ -70,23 +72,16 @@ void raisePyExceptionWithAny( const css::uno::Any &anyExc )
}
/// @throws RuntimeException
static PyRef createClass( const OUString & name, const Runtime &runtime )
static PyRef createClassFromTypeDescription(
std::u16string_view name, typelib_TypeDescription* pType, const Runtime& runtime )
{
// assuming that this is never deleted !
// note I don't have the knowledge how to initialize these type objects correctly !
TypeDescription desc( name );
if( ! desc.is() )
{
throw RuntimeException( "pyuno.getClass: uno exception " + name + " is unknown" );
}
bool isStruct = desc.get()->eTypeClass == typelib_TypeClass_STRUCT;
bool isExc = desc.get()->eTypeClass == typelib_TypeClass_EXCEPTION;
bool isInterface = desc.get()->eTypeClass == typelib_TypeClass_INTERFACE;
bool isStruct = pType->eTypeClass == typelib_TypeClass_STRUCT;
bool isExc = pType->eTypeClass == typelib_TypeClass_EXCEPTION;
bool isInterface = pType->eTypeClass == typelib_TypeClass_INTERFACE;
if( !isStruct && !isExc && ! isInterface )
{
throw RuntimeException( "pyuno.getClass: " + name + "is a " +
OUString::createFromAscii( typeClassToString( static_cast<css::uno::TypeClass>(desc.get()->eTypeClass)) ) +
throw RuntimeException( "pyuno.getClass: " + OUString::Concat(name) + "is a " +
OUString::createFromAscii( typeClassToString( static_cast<css::uno::TypeClass>(pType->eTypeClass)) ) +
", expected EXCEPTION, STRUCT or INTERFACE" );
}
@@ -94,7 +89,7 @@ static PyRef createClass( const OUString & name, const Runtime &runtime )
PyRef base;
if( isInterface )
{
typelib_InterfaceTypeDescription *pDesc = reinterpret_cast<typelib_InterfaceTypeDescription *>(desc.get());
typelib_InterfaceTypeDescription *pDesc = reinterpret_cast<typelib_InterfaceTypeDescription *>(pType);
if( pDesc->pBaseTypeDescription )
{
base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, runtime );
@@ -106,7 +101,7 @@ static PyRef createClass( const OUString & name, const Runtime &runtime )
}
else
{
typelib_CompoundTypeDescription *pDesc = reinterpret_cast<typelib_CompoundTypeDescription*>(desc.get());
typelib_CompoundTypeDescription *pDesc = reinterpret_cast<typelib_CompoundTypeDescription*>(pType);
if( pDesc->pBaseTypeDescription )
{
base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, runtime );
@@ -183,6 +178,79 @@ static PyRef createClass( const OUString & name, const Runtime &runtime )
return ret;
}
static PyRef createEmptyPyTypeForTypeDescription(
const css::uno::Reference<css::reflection::XTypeDescription>& xType)
{
PyRef ret(
PyObject_CallFunctionObjArgs(
reinterpret_cast<PyObject*>(&PyType_Type),
ustring2PyString(xType->getName()).get(),
PyRef(PyTuple_New(0), SAL_NO_ACQUIRE).get(), // bases
PyRef(PyDict_New(), SAL_NO_ACQUIRE).get(),
nullptr),
SAL_NO_ACQUIRE);
return ret;
}
static PyRef createClassForService(
const css::uno::Reference<css::reflection::XServiceTypeDescription2>& xService)
{
PyRef ret = createEmptyPyTypeForTypeDescription(xService);
// Set an attribute on the class for each of the constructors
for (const auto& xConstructor : xService->getConstructors())
{
OUString sName = xConstructor->getName();
// The name is empty for the default constructor
if (sName.isEmpty())
{
if (xConstructor->isDefaultConstructor())
sName = "create";
else
continue;
}
PyObject_SetAttr(
ret.get(),
ustring2PyString(sName).get(),
PyUNO_service_constructor_new(xService, xConstructor).get());
}
return ret;
}
/// @throws RuntimeException
static PyRef createClass( const OUString & name, const Runtime &runtime )
{
// assuming that this is never deleted !
// note I don't have the knowledge how to initialize these type objects correctly !
TypeDescription desc(name);
if (desc.is())
return createClassFromTypeDescription(name, desc.get(), runtime);
// If there’s no type description from the typelib then check if it’s a service using the type
// description manager.
css::uno::Any xType;
try
{
xType = runtime.getImpl()->cargo->xTdMgr->getByHierarchicalName(name);
}
catch (css::container::NoSuchElementException&)
{
// This will flow through to throw a runtime exception below
}
css::uno::Reference<css::reflection::XServiceTypeDescription2> xService;
if ((xType >>= xService) && xService.is())
return createClassForService(xService);
throw RuntimeException("pyuno.getClass: uno exception " + name + " is unknown");
}
bool isInstanceOfStructOrException( PyObject *obj)
{
PyRef attr(
+6
View File
@@ -56,6 +56,8 @@ namespace com::sun::star::container { class XEnumeration; }
namespace com::sun::star::container { class XHierarchicalNameAccess; }
namespace com::sun::star::lang { class XSingleServiceFactory; }
namespace com::sun::star::reflection { class XIdlReflection; }
namespace com::sun::star::reflection { class XServiceConstructorDescription; }
namespace com::sun::star::reflection { class XServiceTypeDescription2; }
namespace com::sun::star::script { class XInvocation2; }
namespace com::sun::star::script { class XInvocationAdapterFactory2; }
namespace com::sun::star::script { class XTypeConverter; }
@@ -179,6 +181,10 @@ PyRef PyUNO_callable_new (
const css::uno::Reference<css::script::XInvocation2> &xInv,
const OUString &methodName );
PyRef PyUNO_service_constructor_new(
const css::uno::Reference<css::reflection::XServiceTypeDescription2>& xService,
const css::uno::Reference<css::reflection::XServiceConstructorDescription>& xConstructor);
PyObject* PyUNO_Type_new (const char *typeName , css::uno::TypeClass t , const Runtime &r );
PyObject* PyUNO_Enum_new( const char *enumBase, const char *enumValue, const Runtime &r );
PyObject* PyUNO_char_new (sal_Unicode c , const Runtime &r);
@@ -0,0 +1,290 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include "pyuno_impl.hxx"
#include <vector>
#include <osl/diagnose.h>
#include <rtl/ustrbuf.hxx>
#include <typelib/typedescription.hxx>
#include <com/sun/star/reflection/XServiceConstructorDescription.hpp>
#include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
#include <com/sun/star/script/CannotConvertException.hpp>
#include <com/sun/star/script/XTypeConverter.hpp>
#include <com/sun/star/uno/XComponentContext.hpp>
namespace pyuno
{
namespace
{
struct PyUNO_service_constructor_Internals
{
css::uno::Reference<css::reflection::XServiceTypeDescription2> xService;
css::uno::Reference<css::reflection::XServiceConstructorDescription> xConstructor;
// The parameter types are lazily converted to TypeDescriptions when the constructor is first
// called.
bool bTypesInitialized = false;
std::vector<css::uno::TypeDescription> xParamTypes;
// Whether the last parameter is a rest parameter
bool bHasRest = false;
void ensureParamTypes();
};
void PyUNO_service_constructor_Internals::ensureParamTypes()
{
if (bTypesInitialized)
return;
css::uno::Sequence<css::uno::Reference<css::reflection::XParameter>> aParams
= xConstructor->getParameters();
for (const auto& xParameter : aParams)
xParamTypes.emplace_back(xParameter->getType()->getName());
bHasRest = aParams.getLength() > 0 && aParams[aParams.getLength() - 1]->isRestParameter();
bTypesInitialized = true;
}
struct PyUNO_service_constructor
{
PyObject_HEAD;
PyUNO_service_constructor_Internals members;
};
void PyUNO_service_constructor_dealloc(PyObject* self)
{
PyUNO_service_constructor* me = reinterpret_cast<PyUNO_service_constructor*>(self);
// members was allocated with the placement new operator so we need to explicitly call the
// destructor
me->members.~PyUNO_service_constructor_Internals();
Py_TYPE(self)->tp_free(self);
}
PyObject* PyUNO_service_constructor_call(PyObject* self, PyObject* args,
SAL_UNUSED_PARAMETER PyObject*)
{
PyUNO_service_constructor* me = reinterpret_cast<PyUNO_service_constructor*>(self);
me->members.ensureParamTypes();
Py_ssize_t nParams = PyTuple_Size(args);
// context, fixed params
sal_Int32 nMinParams = 1 + me->members.xParamTypes.size();
sal_Int32 nMaxParams;
if (me->members.bHasRest)
{
// The last parameter can be empty
nMinParams--;
nMaxParams = SAL_MAX_INT32;
}
else
nMaxParams = nMinParams;
if (nParams < nMinParams || nParams > nMaxParams)
{
OUString sConstructorName = me->members.xConstructor->getName();
if (sConstructorName.isEmpty() && me->members.xConstructor->isDefaultConstructor())
sConstructorName = "create";
OUStringBuffer buf(me->members.xService->getName() + "::" + sConstructorName
+ " requires ");
if (me->members.bHasRest)
buf.append("at least ");
buf.append(OUString::number(nMinParams) + " argument");
if (nMinParams > 1)
buf.append('s');
PyErr_SetString(
PyExc_AttributeError,
OUStringToOString(buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US).getStr());
return nullptr;
}
try
{
Runtime runtime;
css::uno::Any contextAny = runtime.pyObject2Any(PyTuple_GetItem(args, 0));
css::uno::Reference<css::uno::XComponentContext> xContext;
if (!(contextAny >>= xContext) || !xContext.is())
{
PyErr_SetString(PyExc_AttributeError,
"First argument to a service constructor must be an XComponentContext");
return nullptr;
}
css::uno::Sequence<css::uno::Any> aParams(nParams - 1);
css::uno::Any* pParams = aParams.getArray();
for (sal_Int32 i = 0; i < nParams - 1; ++i)
{
css::uno::Any param = runtime.pyObject2Any(PyTuple_GetItem(args, i + 1));
// Use the type of this parameter or the last one if we’re building the rest parameters
std::size_t nConstructorParam
= std::min(std::size_t(i), me->members.xParamTypes.size() - 1);
typelib_TypeDescription* pDestType = me->members.xParamTypes[nConstructorParam].get();
// Try to coax the any to the right type. This is needed for example to allow passing
// None to as an interface reference
pParams[i]
= runtime.getImpl()->cargo->xTypeConverter->convertTo(param, pDestType->pWeakRef);
}
css::uno::Reference<css::uno::XInterface> xInterface
= xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
me->members.xService->getName(), aParams, xContext);
return runtime.any2PyObject(css::uno::Any(std::move(xInterface))).getAcquired();
}
catch (const css::reflection::InvocationTargetException& e)
{
raisePyExceptionWithAny(e.TargetException);
}
catch (const css::script::CannotConvertException& e)
{
raisePyExceptionWithAny(css::uno::Any(e));
}
catch (const css::lang::IllegalArgumentException& e)
{
raisePyExceptionWithAny(css::uno::Any(e));
}
catch (const css::uno::RuntimeException& e)
{
raisePyExceptionWithAny(css::uno::Any(e));
}
return nullptr;
}
PyTypeObject PyUNO_service_constructor_Type = {
PyVarObject_HEAD_INIT(nullptr, 0) "PyUNO_service_constructor",
sizeof(PyUNO_service_constructor),
0,
PyUNO_service_constructor_dealloc,
#if PY_VERSION_HEX >= 0x03080000
0, // Py_ssize_t tp_vectorcall_offset
#else
nullptr, // printfunc tp_print
#endif
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
PyUNO_service_constructor_call,
nullptr,
nullptr,
nullptr,
nullptr,
0,
nullptr,
nullptr,
nullptr,
nullptr,
0,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
0,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
0
#if PY_VERSION_HEX >= 0x03040000
,
nullptr
#if PY_VERSION_HEX >= 0x03080000
,
nullptr // vectorcallfunc tp_vectorcall
#if PY_VERSION_HEX < 0x03090000
#if defined __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
,
nullptr // tp_print
#if defined __clang__
#pragma clang diagnostic pop
#endif
#endif
#if PY_VERSION_HEX >= 0x030C00A1
,
0 // tp_watched
#endif
#if PY_VERSION_HEX >= 0x030D00A4
,
0 // tp_versions_used
#endif
#endif
#endif
};
}
PyRef PyUNO_service_constructor_new(
const css::uno::Reference<css::reflection::XServiceTypeDescription2>& xService,
const css::uno::Reference<css::reflection::XServiceConstructorDescription>& xConstructor)
{
OSL_ENSURE(xService.is(), "xService must be valid");
OSL_ENSURE(xConstructor.is(), "xConstructor must be valid");
PyUNO_service_constructor* self
= PyObject_New(PyUNO_service_constructor, &PyUNO_service_constructor_Type);
if (self == nullptr)
return nullptr; // NULL == Error!
// The memory for members was allocated above but we still need to call the constructor so we’ll
// use a placement new.
new (&self->members) PyUNO_service_constructor_Internals;
self->members.xService = xService;
self->members.xConstructor = xConstructor;
return PyRef(reinterpret_cast<PyObject*>(self), SAL_NO_ACQUIRE);
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+1 -1
View File
@@ -384,7 +384,7 @@ def _uno_import(name, *optargs, **kwargs):
failed = False
try:
# check for structs, exceptions or interfaces
# check for structs, exceptions, interfaces or services
d[class_name] = pyuno.getClass(name + "." + class_name)
except RuntimeException:
# check for enums