Skip to content

Commit 7827529

Browse files
authored
Create PythonFunctionTests.swift
1 parent 44a05a8 commit 7827529

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import XCTest
2+
import PythonKit
3+
4+
class PythonFunctionTests: XCTestCase {
5+
private var canUsePythonFunction: Bool {
6+
let versionMajor = Python.versionInfo.major
7+
let versionMinor = Python.versionInfo.minor
8+
return (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3
9+
}
10+
11+
func testPythonFunction() {
12+
guard canUsePythonFunction else {
13+
return
14+
}
15+
16+
let pythonAdd = PythonFunction { (params: [PythonObject]) in
17+
let lhs = params[0]
18+
let rhs = params[1]
19+
return lhs + rhs
20+
}.pythonObject
21+
22+
let pythonSum = pythonAdd(2, 3)
23+
XCTAssertNotNil(Double(pythonSum))
24+
XCTAssertEqual(pythonSum, 5)
25+
}
26+
27+
// From https://www.geeksforgeeks.org/create-classes-dynamically-in-python
28+
func testPythonClassConstruction() {
29+
guard canUsePythonFunction else {
30+
return
31+
}
32+
33+
let constructor = PythonInstanceMethod { (params: [PythonObject]) in
34+
let `self` = params[0]
35+
let arg = params[1]
36+
`self`.constructor_arg = arg
37+
return Python.None
38+
}
39+
40+
// Instead of calling `print`, use this to test what would be output.
41+
var printOutput: String?
42+
43+
let displayMethod = PythonInstanceMethod { (params: [PythonObject]) in
44+
// let `self` = params[0]
45+
let arg = params[1]
46+
printOutput = String(arg)
47+
return Python.None
48+
}
49+
50+
let classMethodOriginal = PythonInstanceMethod { (params: [PythonObject]) in
51+
// let cls = params[0]
52+
let arg = params[1]
53+
printOutput = String(arg)
54+
return Python.None
55+
}
56+
57+
// Did not explicitly convert `constructor` or `displayMethod` to PythonObject.
58+
// This is intentional, as the `PythonClass` initializer should take any
59+
// `PythonConvertible` and not just `PythonObject`.
60+
let classMethod = Python.classmethod(classMethodOriginal.pythonObject)
61+
62+
let Geeks = PythonClass("Geeks", members: [
63+
// Constructor
64+
"__init__": constructor,
65+
66+
// Data members
67+
"string_attribute": "Geeks 4 geeks!",
68+
"int_attribute": 1706256,
69+
70+
// Member functions
71+
"func_arg": displayMethod,
72+
"class_func": classMethod,
73+
]).pythonObject
74+
75+
let obj = Geeks("constructor argument")
76+
XCTAssertEqual(obj.constructor_arg, "constructor argument")
77+
XCTAssertEqual(obj.string_attribute, "Geeks 4 geeks!")
78+
XCTAssertEqual(obj.int_attribute, 1706256)
79+
80+
obj.func_arg("Geeks for Geeks")
81+
XCTAssertEqual(printOutput, "Geeks for Geeks")
82+
83+
Geeks.class_func("Class Dynamically Created!")
84+
XCTAssertEqual(printOutput, "Class Dynamically Created!")
85+
}
86+
87+
func testPythonClassInheritance() {
88+
guard canUsePythonFunction else {
89+
return
90+
}
91+
92+
var helloOutput: String?
93+
var helloWorldOutput: String?
94+
95+
// Declare subclasses of `Python.Exception`
96+
97+
let HelloException = PythonClass(
98+
"HelloException",
99+
superclasses: [Python.Exception],
100+
members: [
101+
"str_prefix": "HelloException-prefix ",
102+
103+
"__init__": PythonInstanceMethod { (params: [PythonObject]) in
104+
let `self` = params[0]
105+
let message = "hello \(params[1])"
106+
helloOutput = String(message)
107+
108+
// Conventional `super` syntax causes problems; use this instead.
109+
Python.Exception.__init__(self, message)
110+
return Python.None
111+
},
112+
113+
"__str__": PythonInstanceMethod { (`self`: PythonObject) in
114+
return `self`.str_prefix + Python.repr(`self`)
115+
}
116+
]
117+
).pythonObject
118+
119+
let HelloWorldException = PythonClass(
120+
"HelloWorldException",
121+
superclasses: [HelloException],
122+
members: [
123+
"str_prefix": "HelloWorldException-prefix ",
124+
125+
"__init__": PythonInstanceMethod { (params: [PythonObject]) in
126+
let `self` = params[0]
127+
let message = "world \(params[1])"
128+
helloWorldOutput = String(message)
129+
130+
`self`.int_param = params[2]
131+
132+
// Conventional `super` syntax causes problems; use this instead.
133+
HelloException.__init__(self, message)
134+
return Python.None
135+
},
136+
137+
"custom_method": PythonInstanceMethod { (`self`: PythonObject) in
138+
return `self`.int_param
139+
}
140+
]
141+
).pythonObject
142+
143+
// Test that inheritance works as expected
144+
145+
let error1 = HelloException("test 1")
146+
XCTAssertEqual(helloOutput, "hello test 1")
147+
XCTAssertEqual(Python.str(error1), "HelloException-prefix HelloException('hello test 1')")
148+
XCTAssertEqual(Python.repr(error1), "HelloException('hello test 1')")
149+
150+
let error2 = HelloWorldException("test 1", 123)
151+
XCTAssertEqual(helloOutput, "hello world test 1")
152+
XCTAssertEqual(helloWorldOutput, "world test 1")
153+
XCTAssertEqual(Python.str(error2), "HelloWorldException-prefix HelloWorldException('hello world test 1')")
154+
XCTAssertEqual(Python.repr(error2), "HelloWorldException('hello world test 1')")
155+
XCTAssertEqual(error2.custom_method(), 123)
156+
XCTAssertNotEqual(error2.custom_method(), "123")
157+
158+
// Test that subclasses behave like Python exceptions
159+
160+
let testFunction = PythonFunction { (_: [PythonObject]) in
161+
throw HelloWorldException("EXAMPLE ERROR MESSAGE", 2)
162+
}.pythonObject
163+
164+
do {
165+
try testFunction.throwing.dynamicallyCall(withArguments: [])
166+
XCTFail("testFunction did not throw an error.")
167+
} catch PythonError.exception(let error, _) {
168+
guard let description = String(error) else {
169+
XCTFail("A string could not be created from a HelloWorldException.")
170+
return
171+
}
172+
173+
XCTAssertTrue(description.contains("EXAMPLE ERROR MESSAGE"))
174+
XCTAssertTrue(description.contains("HelloWorldException"))
175+
} catch {
176+
XCTFail("Got error that was not a Python exception: \(error.localizedDescription)")
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)