See More

/* -*- mode: c++; indent-tabs-mode: nil -*- */ /** @file QC_PythonProgram.qpp defines the %Qore PythonProgram class */ /* QC_PythonProgram.qpp Qore Programming Language Copyright 2020 - 2026 Qore Technologies, s.r.o. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "QC_PythonProgram.h" /** @defgroup python_constants Python Constants Python module constants */ ///@{ //! The Python version string (e.g. "3.14.2") const PythonVersion = str(PY_VERSION); ///@} //! Program for embedding and executing Python code /** */ qclass PythonProgram [arg=QorePythonProgramData* pp; ns=Python; dom=EMBEDDED_LOGIC]; //! Creates the object and parses and runs the given source code /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source; this is used as the module name for the compiled Python code as well @note - The code is executed after parsing and compiling - Exceptions parsing, compiling, or running the Python code are thrown according to @ref python_exceptions */ PythonProgram::constructor(string source_code, string source_label) [flags=NAMED_ARGS] { ReferenceHolder pp(new QorePythonProgramData(*source_code, *source_label, Py_file_input, xsink), xsink); if (*xsink) { return; } //printd(5, "PythonProgram::constructor() created %p\n", *pp); self->setPrivate(CID_PYTHONPROGRAM, pp.release()); } //! Destroys the interpreter context and invalidates the object /** */ PythonProgram::destructor() { //printd(5, "Qore PythonProgram::destructor() this: %p\n", pp); pp->destructor(xsink); pp->deref(xsink); } //! Runs the program /** @note Exceptions parsing or compiling the Python code are thrown according to @ref python_exceptions */ auto PythonProgram::run() { return pp->run(xsink); } //! Call the given function and return the result /** @param func_name the function name to call @param ... arguments to the function should follow the name converted to Python values as per @ref python_qore_to_python @return the return value of the Python function converted to Qore as per @ref python_python_to_qore @throw NO-FUNCTION the given function cannot be found in the Python program's module dictionary @see @ref python_exceptions */ auto PythonProgram::callFunction(string func_name, ...) { return pp->callFunction(xsink, func_name, args, 1); } //! Call the given function and return the result /** @param function_name the function name to call @param arguments arguments to the function as a list converted to Python values as per @ref python_qore_to_python @return the return value of the Python function converted to Qore as per @ref python_python_to_qore @throw NO-FUNCTION the given function cannot be found in the Python program's module dictionary @see @ref python_exceptions */ auto PythonProgram::callFunctionArgs(string function_name, *softlist arguments) [flags=NAMED_ARGS] { return pp->callFunction(xsink, function_name, arguments); } //! Call the given method and return the result /** @param class_name the name of the class implementing the method to call @param method_name the method to call @param ... arguments to the method should follow the name converted to Python values as per @ref python_qore_to_python @return the return value of the Python method call converted to Qore as per @ref python_python_to_qore @throw NO-CLASS the given class cannot be found in the Python program's module dictionary @throw NO-METHOD the given method cannot be found in the class @see @ref python_exceptions */ auto PythonProgram::callMethod(string class_name, string method_name, ...) { return pp->callMethod(xsink, class_name, method_name, args, 2); } //! Call the given method and return the result /** @param class_name the name of the class implementing the method to call @param method_name the method to call @param arguments arguments to the method as a list converted to Python values as per @ref python_qore_to_python @return the return value of the Python method call converted to Qore as per @ref python_python_to_qore @throw NO-CLASS the given class cannot be found in the Python program's module dictionary @throw NO-METHOD the given method cannot be found in the class @see @ref python_exceptions */ auto PythonProgram::callMethodArgs(string class_name, string method_name, *softlist arguments) [flags=NAMED_ARGS] { return pp->callMethod(xsink, class_name, method_name, arguments); } //! Parse, compile, and evaluate the given statement and return any result; declarations are not persistent /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source @throw PYTHON-COMPILE-ERROR error parsing or compiling the given source @see evalStatementKeep() */ auto PythonProgram::evalStatement(string source_code, string source_label = "stmt") [flags=NAMED_ARGS] { return pp->eval(xsink, *source_code, *source_label, Py_file_input, true); } //! Parse, compile, and evaluate the given statement and return any result; declarations are persistent /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source @throw PYTHON-COMPILE-ERROR error parsing or compiling the given source @see evalStatement() */ auto PythonProgram::evalStatementKeep(string source_code, string source_label = "stmt") [flags=NAMED_ARGS] { return pp->eval(xsink, *source_code, *source_label, Py_file_input, false); } //! Parse, compile, and evaluate the given expression and return any result /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source @throw PYTHON-COMPILE-ERROR error parsing or compiling the given source @note This method uses a shared static PythonProgram object to parse, compile, and evaluate the input string */ auto PythonProgram::evalExpression(string source_code, string source_label = "exp") [flags=NAMED_ARGS] { return pp->eval(xsink, *source_code, *source_label, Py_eval_input, false); } //! Sets the "save object" callback for %Qore objects created from Python code /** @par Example: @code{.py} hash object_cache; code callback = sub (object obj) { # save object in object cache, so it doesn't go out of scope object_cache{obj.uniqueHash()} = obj; } pypgm.setSaveObjectCallback(callback); @endcode @param save_object_callback the callback to save any %Qore objects created in Python code, must take an argument of type @ref object_type "object" Due to the differences in garbage collection approaches between %Qore and Python, %Qore objects must be managed with a deterministic life cycle; JaPythona objects have only weak references to %Qore objects due to the lack of destructors in Python and the lack of determinism in the Python runtime for object lifecycle management. The callback set here will be called any time a %Qore object is created from Python code; if no callback is set, then the standard thread-local implementation is used where %Qore objects are saved in a thread-local hash. @see @ref python_qore_object_lifecycle_management for more information */ PythonProgram::setSaveObjectCallback(*code save_object_callback) [dom=PROCESS;flags=NAMED_ARGS] { pp->setSaveObjectCallback(save_object_callback); } //! Imports the given Qore namespace to the Python program object under the given module path /** @param qore_namespace_path the path to the Qore namespace (ex: \c "::Qore::Thread") @param python_module_path the dot-separated path to the target Python module to be created (ex: \c "qore.qore.thread") @throw IMPORT-NS-ERROR Qore namespace could not be found or the root namespace was given for import */ PythonProgram::importNamespace(string qore_namespace_path, string python_module_path) [flags=NAMED_ARGS] { TempEncodingHelper npath(qore_namespace_path, QCS_DEFAULT, xsink); if (*xsink) { return QoreValue(); } QoreProgram* pgm = getProgram(); const QoreNamespace* ns = pgm->findNamespace(**npath); if (!ns || ns == pgm->getRootNS()) { xsink->raiseException("IMPORT-NS-ERROR", "invalid source namespace '%s'", npath->c_str()); return QoreValue(); } pp->importQoreNamespaceToPython(*ns, *python_module_path, xsink); } //! Creates an alias in the Python program for the given symbol /** @param python_source_path the dotted path to the source definition (ex: \c "some.path.to.sometehing") @param python_target_path the dotted path to the target definition (ex: \c "a.different.path") @throw PYTHON-ALIAS-ERROR there was an error finding the source object or creating the target */ PythonProgram::aliasDefinition(string python_source_path, string python_target_path) [flags=NAMED_ARGS] { TempEncodingHelper srcpath(python_source_path, QCS_DEFAULT, xsink); if (*xsink) { return QoreValue(); } TempEncodingHelper trgpath(python_target_path, QCS_DEFAULT, xsink); if (*xsink) { return QoreValue(); } try { pp->aliasDefinition(**srcpath, **trgpath); } catch (AbstractException& e) { e.convert(xsink); } } //! Parse, compile, and evaluate one or more Python statements and return any result /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source @throw PYTHON-COMPILE-ERROR error parsing or compiling the given source @note - This method uses the PythonProgram context in the current program to parse, compile, and evaluate the input string - Is parsed in a "standalone" mode without being able to access existing symbols in the program @see evalStatementKeep() */ static auto PythonProgram::evalStatement(string source_code, string source_label = "stmt") [flags=NAMED_ARGS] { QorePythonProgram* pypgm = QorePythonProgram::getContext(); assert(pypgm); return pypgm->eval(xsink, *source_code, *source_label, Py_file_input, true); } //! Parse, compile, and evaluate the given code and leave declarations in the Python program object /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source @throw PYTHON-COMPILE-ERROR error parsing or compiling the given source @note This method uses the PythonProgram context in the current program to parse, compile, and evaluate the input string @see evalStatement() */ static auto PythonProgram::evalStatementKeep(string source_code, string source_label = "stmt") [flags=NAMED_ARGS] { QorePythonProgram* pypgm = QorePythonProgram::getContext(); assert(pypgm); return pypgm->eval(xsink, *source_code, *source_label, Py_file_input, false); } //! Parse, compile, and evaluate the given expression and return any result /** @param source_code the Python source to parse and compile @param source_label the label or file name of the source @throw PYTHON-COMPILE-ERROR error parsing or compiling the given source @note This method uses a shared static PythonProgram object to parse, compile, and evaluate the input string */ static auto PythonProgram::evalExpression(string source_code, string source_label = "exp") [flags=NAMED_ARGS] { QorePythonProgram* pypgm = QorePythonProgram::getContext(); assert(pypgm); return pypgm->eval(xsink, *source_code, *source_label, Py_eval_input, false); } //! Returns @ref True if the Python interpreter was built with free-threading support (PEP 703) /** @par Example: @code{.py} if (PythonProgram::isFreeThreading()) { # Free-threading Python - some features may be limited } @endcode @return @ref True if the Python interpreter was built with free-threading support (Py_GIL_DISABLED), @ref False otherwise @note JNI/Java integration is not supported with free-threading Python builds due to memory allocator conflicts; use a GIL-enabled Python build for Java integration @since python 1.3 */ static bool PythonProgram::isFreeThreading() { #ifdef Py_GIL_DISABLED return true; #else return false; #endif } //! Sets the "save object" callback for %Qore objects created from Python code in the root %Qore Program context /** @par Example: @code{.py} hash object_cache; code callback = sub (object obj) { # save object in object cache, so it doesn't go out of scope object_cache{obj.uniqueHash()} = obj; } PythonProgram::setSaveObjectCallback(callback); @endcode @param save_object_callback the callback to save any %Qore objects created in Python code, must take an argument of type @ref object_type "object" Due to the differences in garbage collection approaches between %Qore and Python, %Qore objects must be managed with a deterministic life cycle; JaPythona objects have only weak references to %Qore objects due to the lack of destructors in Python and the lack of determinism in the Python runtime for object lifecycle management. The callback set here will be called any time a %Qore object is created from Python code; if no callback is set, then the standard thread-local implementation is used where %Qore objects are saved in a thread-local hash. @see @ref python_qore_object_lifecycle_management for more information */ static PythonProgram::setSaveObjectCallback(*code save_object_callback) [dom=PROCESS;flags=NAMED_ARGS] { QorePythonProgram* pypgm = QorePythonProgram::getContext(); assert(pypgm); pypgm->setSaveObjectCallback(save_object_callback); }