-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathQC_PythonProgram.qpp
More file actions
347 lines (276 loc) · 14.2 KB
/
Copy pathQC_PythonProgram.qpp
File metadata and controls
347 lines (276 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
/* -*- 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<QorePythonProgramData> 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<auto> 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<auto> 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<string, object> 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<string, object> 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);
}