Skip to content

Commit 4755ed2

Browse files
committed
[GR-34607] Fixes for lxml
PullRequest: graalpython/2070
2 parents 0a1d69a + 2a28b78 commit 4755ed2

36 files changed

+734
-252
lines changed

CHANGELOG.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
This changelog summarizes major changes between GraalVM versions of the Python
44
language runtime. The main focus is on user-observable behavior of the engine.
55

6+
## Version 22.1.0
7+
* String conversion (`__str__`) now calls `toString` for Java objects and `toDisplayString` interop message for foreign objects.
8+
* Improved compatibility with PyPI package `lxml`
9+
610
## Version 22.0.0
711
* Added support for `pyexpat` module.
812
* Added partial support for `PYTHONHASHSEED` environment variable (also available via `HashSeed` context option), currently only affecting hashing in `pyexpat` module.
913
* Implement `_csv` module.
1014
* Improved compatibility with PyPI packages `wheel` and `click`
11-
* String conversion (`__str__`) now calls `toString` for Java objects and `toDisplayString` interop message for foreign objects.
1215

1316
## Version 21.3.0
1417

@@ -49,7 +52,7 @@ language runtime. The main focus is on user-observable behavior of the engine.
4952
* Implement name mangling for private attributes
5053
* Correctly raise an AttributeError when a class defines slots, but not dict
5154
* Fix infinite continuation prompt in REPL when pasting snippets
52-
* Add jarray module for compatiblity with Jython
55+
* Add jarray module for compatibility with Jython
5356
* Fix multiple memory leaks and crashes when running NumPy in a shared engine
5457
* Improved support for Pandas
5558
* Initial support for Matplotlib

docs/user/OsInterface.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ The java backend is the default when GraalVM Python is run via the `Context` API
4444
### Limitations of the emulated backend
4545

4646
GraalVM Python can log info about known incompatibility of functions executed at runtime, which includes the OS interface related functions.
47-
To turn on this logging, use `--log.python.compatibility=FINE` or other desired logging level.
47+
To turn on this logging, use `--log.python.compatibility.level=FINE` or other desired logging level.
4848

4949
Known limitations of the emulated layer are:
5050

graalpython/com.oracle.graal.python.cext/include/Python.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
* supporting this in many cases, it still involves overhead. */
5656
#define CYTHON_USE_TYPE_SLOTS 0
5757
#define CYTHON_USE_PYTYPE_LOOKUP 0
58+
#define CYTHON_UNPACK_METHODS 0
59+
#define CYTHON_FAST_PYCALL 0
5860
#define CYTHON_FAST_PYCCALL 0
5961
#define CYTHON_USE_DICT_VERSIONS 0
6062
#define CYTHON_AVOID_BORROWED_REFS 1

graalpython/com.oracle.graal.python.cext/src/codeobject.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@
4242

4343
PyTypeObject PyCode_Type = PY_TRUFFLE_TYPE("code", &PyType_Type, Py_TPFLAGS_DEFAULT, sizeof(PyTypeObject));
4444

45+
UPCALL_ID(PyCode_NewEmpty)
46+
PyCodeObject* PyCode_NewEmpty(const char *filename, const char *funcname, int firstlineno) {
47+
return (PyCodeObject*)UPCALL_CEXT_O(_jls_PyCode_NewEmpty, polyglot_from_string(filename, SRC_CS), polyglot_from_string(funcname, SRC_CS), firstlineno);
48+
}
49+
4550
UPCALL_ID(PyCode_New);
4651
PyCodeObject* PyCode_New(int argcount, int kwonlyargcount,
4752
int nlocals, int stacksize, int flags,

graalpython/com.oracle.graal.python.cext/src/object.c

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -337,29 +337,66 @@ int PyObject_AsFileDescriptor(PyObject* obj) {
337337
return UPCALL_CEXT_I(_jls_PyObject_AsFileDescriptor, native_to_java(obj));
338338
}
339339

340-
UPCALL_ID(PyTruffle_GetBuiltin);
341-
int PyObject_Print(PyObject* object, FILE* fd, int flags) {
342-
void *openFunc, *args, *kwargs;
343-
void *printfunc, *printargs, *printkwargs;
344-
void *file;
345-
346-
openFunc = UPCALL_CEXT_O(_jls_PyTruffle_GetBuiltin, polyglot_from_string("open", SRC_CS));
347-
args = PyTuple_New(1);
348-
int f = fileno(fd);
349-
PyTuple_SetItem(args, 0, PyLong_FromLong(f));
350-
kwargs = PyDict_New();
351-
int buffering = 1;
352-
PyDict_SetItemString(kwargs, "buffering", PyLong_FromLong(buffering));
353-
PyDict_SetItemString(kwargs, "mode", polyglot_from_string("w", SRC_CS));
354-
file = PyObject_Call(openFunc, args, kwargs);
355-
356-
printfunc = UPCALL_CEXT_O(_jls_PyTruffle_GetBuiltin, polyglot_from_string("print", SRC_CS));
357-
printargs = PyTuple_New(1);
358-
PyTuple_SetItem(printargs, 0, object);
359-
printkwargs = PyDict_New();
360-
PyDict_SetItemString(printkwargs, "file", file);
361-
PyObject_Call(printfunc, printargs, printkwargs);
362-
return 0;
340+
// Taken from CPython
341+
int PyObject_Print(PyObject *op, FILE *fp, int flags)
342+
{
343+
int ret = 0;
344+
clearerr(fp); /* Clear any previous error condition */
345+
if (op == NULL) {
346+
Py_BEGIN_ALLOW_THREADS
347+
fprintf(fp, "<nil>");
348+
Py_END_ALLOW_THREADS
349+
}
350+
else {
351+
if (op->ob_refcnt <= 0) {
352+
/* XXX(twouters) cast refcount to long until %zd is
353+
universally available */
354+
Py_BEGIN_ALLOW_THREADS
355+
fprintf(fp, "<refcnt %ld at %p>",
356+
(long)op->ob_refcnt, (void *)op);
357+
Py_END_ALLOW_THREADS
358+
}
359+
else {
360+
PyObject *s;
361+
if (flags & Py_PRINT_RAW)
362+
s = PyObject_Str(op);
363+
else
364+
s = PyObject_Repr(op);
365+
if (s == NULL)
366+
ret = -1;
367+
else if (PyBytes_Check(s)) {
368+
fwrite(PyBytes_AS_STRING(s), 1,
369+
PyBytes_GET_SIZE(s), fp);
370+
}
371+
else if (PyUnicode_Check(s)) {
372+
PyObject *t;
373+
t = PyUnicode_AsEncodedString(s, "utf-8", "backslashreplace");
374+
if (t == NULL) {
375+
ret = -1;
376+
}
377+
else {
378+
fwrite(PyBytes_AS_STRING(t), 1,
379+
PyBytes_GET_SIZE(t), fp);
380+
Py_DECREF(t);
381+
}
382+
}
383+
else {
384+
PyErr_Format(PyExc_TypeError,
385+
"str() or repr() returned '%.100s'",
386+
s->ob_type->tp_name);
387+
ret = -1;
388+
}
389+
Py_XDECREF(s);
390+
}
391+
}
392+
if (ret == 0) {
393+
if (ferror(fp)) {
394+
PyErr_SetFromErrno(PyExc_OSError);
395+
clearerr(fp);
396+
ret = -1;
397+
}
398+
}
399+
return ret;
363400
}
364401

365402
// taken from CPython "Objects/object.c"

graalpython/com.oracle.graal.python.cext/src/typeobject.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ static void inherit_slots(PyTypeObject *type, PyTypeObject *base) {
343343
}
344344
/* COPYSLOT(tp_call); */
345345
}
346+
{
347+
COPYSLOT(tp_iter);
348+
COPYSLOT(tp_iternext);
349+
}
346350

347351
if ((type->tp_flags & Py_TPFLAGS_HAVE_FINALIZE) &&
348352
(base->tp_flags & Py_TPFLAGS_HAVE_FINALIZE)) {

graalpython/com.oracle.graal.python.cext/src/unicodeobject.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ Py_UNICODE* PyUnicode_AsUnicode(PyObject *unicode) {
408408

409409
UPCALL_ID(PyTruffle_Unicode_AsWideChar);
410410
Py_UNICODE* PyUnicode_AsUnicodeAndSize(PyObject *unicode, Py_ssize_t *size) {
411-
PyObject* bytes = UPCALL_CEXT_O(_jls_PyTruffle_Unicode_AsWideChar, native_to_java(unicode), Py_UNICODE_SIZE, native_to_java(Py_None), ERROR_MARKER);
411+
PyObject* bytes = UPCALL_CEXT_O(_jls_PyTruffle_Unicode_AsWideChar, native_to_java(unicode), Py_UNICODE_SIZE, ERROR_MARKER);
412412
if (bytes != NULL) {
413413
// exclude null terminator at the end
414414
*size = PyBytes_Size(bytes) / Py_UNICODE_SIZE;
@@ -670,3 +670,8 @@ UPCALL_ID(PyUnicode_Contains)
670670
int PyUnicode_Contains(PyObject *str, PyObject *substr) {
671671
return UPCALL_CEXT_I(_jls_PyUnicode_Contains, native_to_java(str), native_to_java(substr));
672672
}
673+
674+
UPCALL_ID(PyUnicode_Split)
675+
PyObject* PyUnicode_Split(PyObject *s, PyObject *sep, Py_ssize_t maxsplit) {
676+
return UPCALL_CEXT_O(_jls_PyUnicode_Split, native_to_java(s), native_to_java(sep), maxsplit);
677+
}

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_codeobject.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@ def compile_module(self, name):
6262

6363
testmod = type(sys)("foo")
6464

65+
test_PyCode_NewEmpty = CPyExtFunction(
66+
lambda args: args,
67+
lambda: (
68+
("file.c", "myfunc", 54),
69+
),
70+
resultspec="O",
71+
argspec="ssi",
72+
arguments=["char* filename", "char* funcname", "int firstlineno"],
73+
cmpfunc=lambda cr, pr: pr[0] == cr.co_filename and pr[1] == cr.co_name and pr[2] == cr.co_firstlineno,
74+
)
75+
6576
test_PyCode_New = CPyExtFunction(
6677
lambda args: args,
6778
lambda: (

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_memoryview.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@
7474
'''
7575

7676

77+
def decode_ucs(mv, kind):
78+
b = bytes(mv)
79+
if kind == 1:
80+
return b.decode('latin-1')
81+
elif kind == 2:
82+
return b.decode('utf-16')
83+
else:
84+
assert kind == 4
85+
return b.decode('utf-32')
86+
87+
88+
def compare_unicode_bytes_with_kind(x, y):
89+
if isinstance(x, BaseException) and isinstance(y, BaseException):
90+
return type(x) == type(y)
91+
elif isinstance(x, BaseException) or isinstance(y, BaseException):
92+
return False
93+
else:
94+
return decode_ucs(*x) == decode_ucs(*y)
95+
96+
7797
class TestPyMemoryView(CPyExtTestCase):
7898
def compile_module(self, name):
7999
type(self).mro()[1].__dict__["test_%s" % name].create_module(name)
@@ -167,6 +187,54 @@ def compile_module(self, name):
167187
cmpfunc=unhandled_error_compare_with_message,
168188
)
169189

190+
# Test creating memoryview on top of a pointer that is in fact a managed bytes object wrapper
191+
test_memoryview_frommemory_bytes_wrapper = CPyExtFunction(
192+
lambda args: memoryview(args[0])[args[1]],
193+
lambda: (
194+
(b"a", 0),
195+
(b"abcdefgh", 5),
196+
(b"abcdefgh", 8),
197+
),
198+
code='''
199+
static PyObject* test_frommemory_bytes(PyObject* input, PyObject *key) {
200+
PyObject *mv = PyMemoryView_FromMemory(PyBytes_AS_STRING(input), PyBytes_GET_SIZE(input), PyBUF_READ);
201+
if (!mv)
202+
return NULL;
203+
PyObject *item = PyObject_GetItem(mv, key);
204+
Py_DECREF(mv);
205+
return item;
206+
}
207+
''',
208+
resultspec='O',
209+
argspec='OO',
210+
arguments=["PyObject* input", "PyObject* key"],
211+
callfunction="test_frommemory_bytes",
212+
cmpfunc=unhandled_error_compare,
213+
)
214+
215+
# Test creating memoryview on top of a pointer that is in fact a managed unicode object wrapper
216+
test_memoryview_frommemory_unicode_wrapper = CPyExtFunction(
217+
lambda args: (memoryview(args[0].encode('utf-32')), 4),
218+
lambda: (
219+
("asdf",),
220+
("ニャー",),
221+
# ("😂",), # TODO doesn't work because PyUnicode_GET_LENGTH returns 2
222+
),
223+
code='''
224+
static PyObject* test_frommemory_unicode(PyObject* input) {
225+
PyObject *mv = PyMemoryView_FromMemory(PyUnicode_DATA(input), PyUnicode_GET_LENGTH(input) * PyUnicode_KIND(input), PyBUF_READ);
226+
if (!mv)
227+
return NULL;
228+
return Py_BuildValue("Oi", mv, PyUnicode_KIND(input));
229+
}
230+
''',
231+
resultspec='O',
232+
argspec='O',
233+
arguments=["PyObject* input"],
234+
callfunction="test_frommemory_unicode",
235+
cmpfunc=compare_unicode_bytes_with_kind,
236+
)
237+
170238
test_memoryview_tolist = CPyExtFunction(
171239
lambda args: args[0],
172240
lambda: [

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_method.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@
3636
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
39+
import types
3940

40-
from . import CPyExtType
41+
from . import CPyExtType, CPyExtTestCase, unhandled_error_compare, CPyExtFunction
4142

4243
__dir__ = __file__.rpartition("/")[0]
4344

@@ -101,3 +102,20 @@ def test_methods(self):
101102
assert obj.meth_static_noargs() == (None, None)
102103
assert obj.meth_static_varargs(1, 2, 3) == (None, (1, 2, 3))
103104
assert obj.meth_static_varargs_keywords(1, 2, 3, a=1, b=2) == (None, (1, 2, 3), {'a': 1, 'b': 2})
105+
106+
107+
class TestPyMethod(CPyExtTestCase):
108+
def compile_module(self, name):
109+
type(self).mro()[1].__dict__["test_%s" % name].create_module(name)
110+
super().compile_module(name)
111+
112+
test_PyMethod_New = CPyExtFunction(
113+
lambda args: types.MethodType(*args),
114+
lambda: (
115+
(list.append, 6),
116+
),
117+
resultspec="O",
118+
argspec='OO',
119+
arguments=["PyObject* func", "PyObject* self"],
120+
cmpfunc=unhandled_error_compare
121+
)

0 commit comments

Comments
 (0)