Skip to content

Commit c4cfc28

Browse files
committed
Add capsule repr
1 parent 75b92bd commit c4cfc28

File tree

4 files changed

+135
-4
lines changed

4 files changed

+135
-4
lines changed

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
22
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
33
#
44
# The Universal Permissive License (UPL), Version 1.0
@@ -37,12 +37,21 @@
3737
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3838
# SOFTWARE.
3939
import gc
40+
import re
41+
4042
import os
4143
import time
4244

4345
from . import CPyExtTestCase, CPyExtFunction, unhandled_error_compare, CPyExtType
4446

4547

48+
def _capsule_repr_cmpfunc(a, b):
49+
if isinstance(a, str) and (b, str):
50+
a = re.sub("0x[a-f0-9]+", "0xaaa", a)
51+
b = re.sub("0x[a-f0-9]+", "0xaaa", b)
52+
return unhandled_error_compare(a, b)
53+
54+
4655
class TestPyCapsule(CPyExtTestCase):
4756
test_PyCapsule_CheckExact = CPyExtFunction(
4857
lambda args: True,
@@ -150,6 +159,24 @@ class TestPyCapsule(CPyExtTestCase):
150159
cmpfunc=unhandled_error_compare
151160
)
152161

162+
test_PyCapsule__repr__ = CPyExtFunction(
163+
lambda args: f'<capsule object "{args[0]}" at 0xaaaa>',
164+
lambda: (
165+
("hello", 0xDEADBEEF),
166+
),
167+
code='''
168+
PyObject* wrap_PyCapsule__repr__(char * name, Py_ssize_t ptr) {
169+
PyObject* capsule = PyCapsule_New((void*)ptr, name, NULL);
170+
return PyObject_Repr(capsule);
171+
}
172+
''',
173+
resultspec="O",
174+
argspec='sn',
175+
arguments=["char* name", "Py_ssize_t ptr"],
176+
callfunction="wrap_PyCapsule__repr__",
177+
cmpfunc=_capsule_repr_cmpfunc,
178+
)
179+
153180
if os.environ.get('GRAALPYTEST_RUN_GC_TESTS'):
154181
def test_capsule_destructor(self):
155182
Tester = CPyExtType(
@@ -161,7 +188,7 @@ def test_capsule_destructor(self):
161188
PyDict_SetItemString(contents, "destructor_was_here", Py_NewRef(Py_True));
162189
Py_DECREF(contents);
163190
}
164-
191+
165192
static PyObject* create_capsule(PyObject* unused, PyObject* contents) {
166193
return PyCapsule_New(Py_NewRef(contents), "capsule", capsule_destructor);
167194
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@
269269
import com.oracle.graal.python.builtins.objects.thread.ThreadLocalBuiltins;
270270
import com.oracle.graal.python.builtins.objects.tokenize.TokenizerIterBuiltins;
271271
import com.oracle.graal.python.builtins.objects.traceback.TracebackBuiltins;
272+
import com.oracle.graal.python.builtins.objects.tuple.CapsuleBuiltins;
272273
import com.oracle.graal.python.builtins.objects.tuple.InstantiableStructSequenceBuiltins;
273274
import com.oracle.graal.python.builtins.objects.tuple.StructSequenceBuiltins;
274275
import com.oracle.graal.python.builtins.objects.tuple.TupleBuiltins;
@@ -1251,7 +1252,7 @@ def takewhile(predicate, iterable):
12511252
// CPython uses separate keys, values, items python types for the iterators.
12521253
ContextIterator("context_iterator", PythonObject, newBuilder().publishInModule(J__CONTEXTVARS).slots(ContextIteratorBuiltins.SLOTS)),
12531254

1254-
Capsule("PyCapsule", PythonObject, newBuilder().basetype()),
1255+
Capsule("PyCapsule", PythonObject, newBuilder().basetype().slots(CapsuleBuiltins.SLOTS)),
12551256

12561257
PTokenizerIter("TokenizerIter", PythonObject, newBuilder().publishInModule("_tokenize").basetype().slots(TokenizerIterBuiltins.SLOTS)),
12571258

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructAccess.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -354,6 +354,10 @@ static int readManaged(Object pointer, long offset,
354354
protected static boolean isCharSigned() {
355355
return CConstants.CHAR_MIN.longValue() < 0;
356356
}
357+
358+
public static ReadByteNode getUncached() {
359+
return CStructAccessFactory.ReadByteNodeGen.getUncached();
360+
}
357361
}
358362

359363
@ImportStatic(PGuards.class)
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.objects.tuple;
42+
43+
import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
44+
45+
import java.util.Collections;
46+
import java.util.List;
47+
48+
import com.oracle.graal.python.annotations.Slot;
49+
import com.oracle.graal.python.annotations.Slot.SlotKind;
50+
import com.oracle.graal.python.builtins.CoreFunctions;
51+
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
52+
import com.oracle.graal.python.builtins.PythonBuiltins;
53+
import com.oracle.graal.python.builtins.objects.capsule.PyCapsule;
54+
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
55+
import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess;
56+
import com.oracle.graal.python.builtins.objects.type.TpSlots;
57+
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
58+
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
59+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
60+
import com.oracle.truffle.api.dsl.GenerateNodeFactory;
61+
import com.oracle.truffle.api.dsl.NodeFactory;
62+
import com.oracle.truffle.api.dsl.Specialization;
63+
import com.oracle.truffle.api.interop.InteropLibrary;
64+
import com.oracle.truffle.api.strings.TruffleString;
65+
66+
@CoreFunctions(extendClasses = PythonBuiltinClassType.Capsule)
67+
public class CapsuleBuiltins extends PythonBuiltins {
68+
69+
public static final TpSlots SLOTS = CapsuleBuiltinsSlotsGen.SLOTS;
70+
71+
@Override
72+
protected List<? extends NodeFactory<? extends PythonBuiltinBaseNode>> getNodeFactories() {
73+
return Collections.emptyList();
74+
}
75+
76+
@Slot(value = SlotKind.tp_repr, isComplex = true)
77+
@GenerateNodeFactory
78+
abstract static class ReprNode extends PythonUnaryBuiltinNode {
79+
80+
@Specialization
81+
@TruffleBoundary
82+
static Object repr(PyCapsule self) {
83+
String name;
84+
if (self.getNamePtr() == null || InteropLibrary.getUncached().isNull(self.getNamePtr())) {
85+
name = "NULL";
86+
} else {
87+
StringBuilder builder = new StringBuilder("\"");
88+
int i = 0;
89+
byte b;
90+
while ((b = CStructAccess.ReadByteNode.getUncached().readArrayElement(self.getNamePtr(), i++)) != 0) {
91+
builder.append((char) b);
92+
}
93+
builder.append('"');
94+
name = builder.toString();
95+
}
96+
return TruffleString.fromJavaStringUncached(String.format("<capsule object %s at 0x%s>", name, PythonAbstractNativeObject.systemHashCodeAsHexString(self)), TS_ENCODING);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)