Skip to content

Commit 94cf40f

Browse files
committed
[GR-60096] Support passing positional (*args) and keyword arguments (**kwargs) from Java to Python
PullRequest: graalpython/3592
2 parents bcb68c8 + 387bc23 commit 94cf40f

File tree

6 files changed

+1306
-3
lines changed

6 files changed

+1306
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright (c) 2024, 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.test.integration.interop;
42+
43+
import static org.junit.Assert.assertEquals;
44+
import static org.junit.Assert.assertTrue;
45+
46+
import org.graalvm.polyglot.Context;
47+
import org.graalvm.polyglot.Source;
48+
import org.graalvm.polyglot.Value;
49+
50+
import com.oracle.graal.python.test.integration.PythonTests;
51+
import java.io.ByteArrayOutputStream;
52+
53+
import org.graalvm.python.embedding.KeywordArguments;
54+
import org.graalvm.python.embedding.PositionalArguments;
55+
import org.junit.After;
56+
import org.junit.Before;
57+
import org.junit.Test;
58+
59+
import java.util.HashMap;
60+
import java.util.List;
61+
import java.util.Map;
62+
63+
public class ArgumentsTest extends PythonTests {
64+
65+
private ByteArrayOutputStream out;
66+
private Context context;
67+
private ByteArrayOutputStream err;
68+
69+
@Before
70+
public void setUpTest() {
71+
out = new ByteArrayOutputStream();
72+
err = new ByteArrayOutputStream();
73+
Context.Builder builder = Context.newBuilder();
74+
builder.allowExperimentalOptions(true);
75+
builder.allowAllAccess(true);
76+
builder.out(out);
77+
builder.err(err);
78+
context = builder.build();
79+
}
80+
81+
@After
82+
public void tearDown() {
83+
context.close();
84+
}
85+
86+
public interface TestPositionalArgsLong {
87+
long fn(PositionalArguments args);
88+
}
89+
90+
@Test
91+
public void testPositionalArgsLength() {
92+
String source = """
93+
def fn (*args)->int:
94+
return len(args)
95+
""";
96+
97+
TestPositionalArgsLong module = context.eval(Source.create("python", source)).as(TestPositionalArgsLong.class);
98+
assertEquals(1, module.fn(PositionalArguments.of(22)));
99+
assertEquals(2, module.fn(PositionalArguments.of(1, null)));
100+
assertEquals(2, module.fn(PositionalArguments.of(null, 2)));
101+
assertEquals(5, module.fn(PositionalArguments.of(1, 2, 3, 4, 5)));
102+
assertEquals(0, module.fn(PositionalArguments.of()));
103+
104+
assertEquals(1, module.fn(PositionalArguments.from(List.of(2))));
105+
assertEquals(2, module.fn(PositionalArguments.from(List.of(2, 3))));
106+
107+
assertEquals(3, module.fn(PositionalArguments.of(new Object[]{2, 3, 4})));
108+
assertEquals(1, module.fn(PositionalArguments.of(new Object[]{null})));
109+
}
110+
111+
@Test
112+
public void testPositionalArgsWithNoneValue() {
113+
String source = """
114+
def fn (*args):
115+
result = 0;
116+
for arg in args:
117+
if arg is None:
118+
result = result + 1;
119+
return result
120+
""";
121+
122+
TestPositionalArgsLong module = context.eval(Source.create("python", source)).as(TestPositionalArgsLong.class);
123+
assertEquals(0, module.fn(PositionalArguments.of(22)));
124+
assertEquals(1, module.fn(PositionalArguments.of(1, null)));
125+
assertEquals(1, module.fn(PositionalArguments.of(null, 2)));
126+
assertEquals(3, module.fn(PositionalArguments.of(null, null, null)));
127+
assertEquals(1, module.fn(PositionalArguments.of(new Object[]{null})));
128+
129+
Value none = context.eval(Source.create("python", "a = None")).getMember("a");
130+
assertEquals(1, module.fn(PositionalArguments.of(none)));
131+
assertEquals(3, module.fn(PositionalArguments.of(none, null, none)));
132+
assertEquals(2, module.fn(PositionalArguments.of(new Object[]{none, null})));
133+
}
134+
135+
public interface TestPositionalArgs01 {
136+
String fn(Object a, Object b, PositionalArguments args);
137+
}
138+
139+
@Test
140+
public void testPositionalArgs01() {
141+
String source = """
142+
def fn (a, b, *args):
143+
result = str(a) + str(b);
144+
for arg in args:
145+
result = result + str(arg);
146+
return result
147+
""";
148+
TestPositionalArgs01 module = context.eval(Source.create("python", source)).as(TestPositionalArgs01.class);
149+
assertEquals("12", module.fn(1, 2, PositionalArguments.of()));
150+
assertEquals("123", module.fn(1, 2, PositionalArguments.of(3)));
151+
assertEquals("123Ahoj", module.fn(1, 2, PositionalArguments.of(3, "Ahoj")));
152+
assertEquals("123AhojTrue", module.fn(1, 2, PositionalArguments.of(3, "Ahoj", true)));
153+
assertEquals("123AhojTrueNone", module.fn(1, 2, PositionalArguments.of(3, "Ahoj", true, null)));
154+
}
155+
156+
public interface TestPositionalArgs02 {
157+
String fn(Object a);
158+
159+
String fn(Object a, PositionalArguments args);
160+
161+
String fn(PositionalArguments args);
162+
163+
String fn(Object a, Object b);
164+
165+
String fn(Object a, Object b, PositionalArguments args);
166+
}
167+
168+
@Test
169+
public void testPositionalArgs02() {
170+
String source = """
171+
def fn (a, b="correct", *args):
172+
result = str(a) + str(b)
173+
for arg in args:
174+
result = result + str(arg)
175+
return result
176+
""";
177+
TestPositionalArgs02 module = context.eval(Source.create("python", source)).as(TestPositionalArgs02.class);
178+
assertEquals("1correct", module.fn(1));
179+
assertEquals("12", module.fn(1, 2));
180+
assertEquals("1only one", module.fn(1, PositionalArguments.of("only one")));
181+
assertEquals("1only oneand two", module.fn(1, PositionalArguments.of("only one", "and two")));
182+
assertEquals("12", module.fn(1, 2, PositionalArguments.of()));
183+
assertEquals("123", module.fn(1, 2, PositionalArguments.of(3)));
184+
assertEquals("123Ahoj", module.fn(1, 2, PositionalArguments.of(3, "Ahoj")));
185+
assertEquals("123AhojTrue", module.fn(1, 2, PositionalArguments.of(3, "Ahoj", true)));
186+
assertEquals("123AhojTrueNone", module.fn(1, 2, PositionalArguments.of(3, "Ahoj", true, null)));
187+
}
188+
189+
public interface TestKeywordArgs01 {
190+
String fn();
191+
192+
String fn(KeywordArguments kwArgs);
193+
}
194+
195+
@Test
196+
public void testKeywordArgs01() {
197+
String source = """
198+
def fn (**kwArgs):
199+
result = ''
200+
for key, value in kwArgs.items():
201+
result = result + f'[{key}:{str(value)}],'
202+
return result
203+
""";
204+
TestKeywordArgs01 module = context.eval(Source.create("python", source)).as(TestKeywordArgs01.class);
205+
assertEquals("", module.fn());
206+
assertEquals("", module.fn(KeywordArguments.from(new HashMap<>())));
207+
assertEquals("[jedna:1],", module.fn(KeywordArguments.of("jedna", 1)));
208+
209+
Value none = context.eval(Source.create("python", "a = None")).getMember("a");
210+
Map<String, Object> keyArgs = new HashMap<>();
211+
keyArgs.put("jedna", 1);
212+
keyArgs.put("true", true);
213+
keyArgs.put("null", none);
214+
215+
String result = module.fn(KeywordArguments.from(keyArgs));
216+
assertTrue(result.contains("[true:True],"));
217+
assertTrue(result.contains("[jedna:1],"));
218+
assertTrue(result.contains("[null:None],"));
219+
}
220+
}

0 commit comments

Comments
 (0)