Skip to content

Commit 8981b43

Browse files
authored
Merge pull request #2 from philipturner/patch-5-commits
Patch 5 commits
2 parents d59167a + 7827529 commit 8981b43

File tree

2 files changed

+179
-82
lines changed

2 files changed

+179
-82
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+
}

Tests/PythonKitTests/PythonRuntimeTests.swift

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -326,86 +326,4 @@ class PythonRuntimeTests: XCTestCase {
326326
}
327327
XCTAssertEqual(bytes, otherBytes)
328328
}
329-
330-
private var canUsePythonFunction: Bool {
331-
let versionMajor = Python.versionInfo.major
332-
let versionMinor = Python.versionInfo.minor
333-
return (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3
334-
}
335-
336-
func testPythonFunction() {
337-
guard canUsePythonFunction else {
338-
return
339-
}
340-
341-
let pythonAdd = PythonFunction { (params: [PythonObject]) in
342-
let lhs = params[0]
343-
let rhs = params[1]
344-
return lhs + rhs
345-
}.pythonObject
346-
347-
let pythonSum = pythonAdd(2, 3)
348-
XCTAssertNotNil(Double(pythonSum))
349-
XCTAssertEqual(pythonSum, 5)
350-
}
351-
352-
// From https://www.geeksforgeeks.org/create-classes-dynamically-in-python
353-
func testPythonClassConstruction() {
354-
guard canUsePythonFunction else {
355-
return
356-
}
357-
358-
let constructor = PythonInstanceMethod { (params: [PythonObject]) in
359-
let `self` = params[0]
360-
let arg = params[1]
361-
`self`.constructor_arg = arg
362-
return Python.None
363-
}
364-
365-
// Instead of calling `print`, use this to test what would be output.
366-
var printOutput: String?
367-
368-
let displayMethod = PythonInstanceMethod { (params: [PythonObject]) in
369-
// let `self` = params[0]
370-
let arg = params[1]
371-
printOutput = String(arg)
372-
return Python.None
373-
}
374-
375-
let classMethodOriginal = PythonInstanceMethod { (params: [PythonObject]) in
376-
// let cls = params[0]
377-
let arg = params[1]
378-
printOutput = String(arg)
379-
return Python.None
380-
}
381-
382-
// Did not explicitly convert `constructor` or `displayMethod` to PythonObject.
383-
// This is intentional, as the `PythonClass` initializer should take any
384-
// `PythonConvertible` and not just `PythonObject`.
385-
let classMethod = Python.classmethod(classMethodOriginal.pythonObject)
386-
387-
let Geeks = PythonClass("Geeks", members: [
388-
// Constructor
389-
"__init__": constructor,
390-
391-
// Data members
392-
"string_attribute": "Geeks 4 geeks!",
393-
"int_attribute": 1706256,
394-
395-
// Member functions
396-
"func_arg": displayMethod,
397-
"class_func": classMethod,
398-
]).pythonObject
399-
400-
let obj = Geeks("constructor argument")
401-
XCTAssertEqual(obj.constructor_arg, "constructor argument")
402-
XCTAssertEqual(obj.string_attribute, "Geeks 4 geeks!")
403-
XCTAssertEqual(obj.int_attribute, 1706256)
404-
405-
obj.func_arg("Geeks for Geeks")
406-
XCTAssertEqual(printOutput, "Geeks for Geeks")
407-
408-
Geeks.class_func("Class Dynamically Created!")
409-
XCTAssertEqual(printOutput, "Class Dynamically Created!")
410-
}
411329
}

0 commit comments

Comments
 (0)