├── .github └── workflows │ └── continuous-integration.yml ├── .gitignore ├── .gitkeep ├── LICENSE.txt ├── Package.swift ├── PythonKit ├── NumpyConversion.swift ├── Python.swift ├── PythonLibrary+Symbols.swift └── PythonLibrary.swift ├── README.md └── Tests └── PythonKitTests ├── NumpyConversionTests.swift ├── PythonFunctionTests.swift └── PythonRuntimeTests.swift /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | continuous-integration: 9 | strategy: 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macos-latest 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: actions/setup-python@v5 18 | with: 19 | python-version: '3.x' 20 | - name: Test 21 | run: swift test --enable-test-discovery 22 | if: runner.os != 'Windows' 23 | env: 24 | PYTHON_VERSION: 3 25 | PYTHON_LOADER_LOGGING: TRUE 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Hidden Files 2 | .* 3 | !.gitignore 4 | !.gitkeep 5 | !.github 6 | !.travis.yml 7 | 8 | # Swift 9 | /build/ 10 | /Package.resolved 11 | 12 | # Python 13 | *.pyc 14 | 15 | # Temporary Items 16 | *.tmp 17 | *.tmp.* 18 | temp 19 | 20 | # Virtual Environments 21 | /venv*/ 22 | 23 | # Configuration Override 24 | *.override.* 25 | 26 | # Extra Directories 27 | /Assets/ 28 | /Extra/ 29 | 30 | # Xcode 31 | xcuserdata/ 32 | *.xcscmblueprint 33 | *.xccheckout 34 | -------------------------------------------------------------------------------- /.gitkeep: -------------------------------------------------------------------------------- 1 | 55307D11-9B02-4882-B275-49222602DC5E 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | 205 | ## Runtime Library Exception to the Apache 2.0 License: ## 206 | 207 | 208 | As an exception, if you use this Software to compile your source code and 209 | portions of this Software are embedded into the binary product as a result, 210 | you may redistribute such product without providing attribution as would 211 | otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. 212 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "PythonKit", 7 | products: [ 8 | .library( 9 | name: "PythonKit", 10 | targets: ["PythonKit"] 11 | ) 12 | ], 13 | targets: [ 14 | .target( 15 | name: "PythonKit", 16 | path: "PythonKit" 17 | ), 18 | .testTarget( 19 | name: "PythonKitTests", 20 | dependencies: ["PythonKit"] 21 | ), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /PythonKit/NumpyConversion.swift: -------------------------------------------------------------------------------- 1 | //===-- NumpyConversion.swift ---------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift.org open source project 4 | // 5 | // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 | // 11 | //===----------------------------------------------------------------------===// 12 | // 13 | // This file defines the `ConvertibleFromNumpyArray` protocol for bridging 14 | // `numpy.ndarray`. 15 | // 16 | //===----------------------------------------------------------------------===// 17 | 18 | /// The `numpy` Python module. 19 | /// Note: Global variables are lazy, so the following declaration won't produce 20 | /// a Python import error until it is first used. 21 | private let np = Python.import("numpy") 22 | private let ctypes = Python.import("ctypes") 23 | 24 | /// A type that can be initialized from a `numpy.ndarray` instance represented 25 | /// as a `PythonObject`. 26 | public protocol ConvertibleFromNumpyArray { 27 | init?(numpy: PythonObject) 28 | } 29 | 30 | /// A type that is bitwise compatible with one or more NumPy scalar types. 31 | public protocol NumpyScalarCompatible { 32 | /// The NumPy scalar types that this type is bitwise compatible with. Must 33 | /// be nonempty. 34 | static var numpyScalarTypes: [PythonObject] { get } 35 | /// The Python `ctypes` scalar type corresponding to this type. 36 | static var ctype: PythonObject { get } 37 | } 38 | 39 | extension Bool : NumpyScalarCompatible { 40 | public static let numpyScalarTypes = [np.bool_, Python.bool] 41 | public static var ctype: PythonObject { return ctypes.c_bool } 42 | } 43 | 44 | extension UInt8 : NumpyScalarCompatible { 45 | public static let numpyScalarTypes = [np.uint8] 46 | public static var ctype: PythonObject { return ctypes.c_uint8 } 47 | } 48 | 49 | extension Int8 : NumpyScalarCompatible { 50 | public static let numpyScalarTypes = [np.int8] 51 | public static var ctype: PythonObject { return ctypes.c_int8 } 52 | } 53 | 54 | extension UInt16 : NumpyScalarCompatible { 55 | public static let numpyScalarTypes = [np.uint16] 56 | public static var ctype: PythonObject { return ctypes.c_uint16 } 57 | } 58 | 59 | extension Int16 : NumpyScalarCompatible { 60 | public static let numpyScalarTypes = [np.int16] 61 | public static var ctype: PythonObject { return ctypes.c_int16 } 62 | } 63 | 64 | extension UInt32 : NumpyScalarCompatible { 65 | public static let numpyScalarTypes = [np.uint32] 66 | public static var ctype: PythonObject { return ctypes.c_uint32 } 67 | } 68 | 69 | extension Int32 : NumpyScalarCompatible { 70 | public static let numpyScalarTypes = [np.int32] 71 | public static var ctype: PythonObject { return ctypes.c_int32 } 72 | } 73 | 74 | extension UInt64 : NumpyScalarCompatible { 75 | public static let numpyScalarTypes = [np.uint64] 76 | public static var ctype: PythonObject { return ctypes.c_uint64 } 77 | } 78 | 79 | extension Int64 : NumpyScalarCompatible { 80 | public static let numpyScalarTypes = [np.int64] 81 | public static var ctype: PythonObject { return ctypes.c_int64 } 82 | } 83 | 84 | extension Float : NumpyScalarCompatible { 85 | public static let numpyScalarTypes = [np.float32] 86 | public static var ctype: PythonObject { return ctypes.c_float } 87 | } 88 | 89 | extension Double : NumpyScalarCompatible { 90 | public static let numpyScalarTypes = [np.float64] 91 | public static var ctype: PythonObject { return ctypes.c_double } 92 | } 93 | 94 | extension Array : ConvertibleFromNumpyArray 95 | where Element : NumpyScalarCompatible { 96 | /// Creates an `Array` with the same shape and scalars as the specified 97 | /// `numpy.ndarray` instance. 98 | /// 99 | /// - Parameter numpyArray: The `numpy.ndarray` instance to convert. 100 | /// - Precondition: The `numpy` Python package must be installed. 101 | /// - Returns: `numpyArray` converted to an `Array`. Returns `nil` if 102 | /// `numpyArray` is not 1-D or does not have a compatible scalar `dtype`. 103 | public init?(numpy numpyArray: PythonObject) { 104 | // Check if input is a `numpy.ndarray` instance. 105 | guard Python.isinstance(numpyArray, np.ndarray) == true else { 106 | return nil 107 | } 108 | // Check if the dtype of the `ndarray` is compatible with the `Element` 109 | // type. 110 | guard Element.numpyScalarTypes.contains(numpyArray.dtype) else { 111 | return nil 112 | } 113 | 114 | // Only 1-D `ndarray` instances can be converted to `Array`. 115 | let pyShape = numpyArray.__array_interface__["shape"] 116 | guard let shape = Array(pyShape) else { return nil } 117 | guard shape.count == 1 else { 118 | return nil 119 | } 120 | 121 | // Make sure that the array is contiguous in memory. This does a copy if 122 | // the array is not already contiguous in memory. 123 | let contiguousNumpyArray = np.ascontiguousarray(numpyArray) 124 | 125 | guard let ptrVal = 126 | UInt(contiguousNumpyArray.__array_interface__["data"].tuple2.0) else { 127 | return nil 128 | } 129 | guard let ptr = UnsafePointer(bitPattern: ptrVal) else { 130 | fatalError("numpy.ndarray data pointer was nil") 131 | } 132 | // This code avoids constructing and initialize from `UnsafeBufferPointer` 133 | // because that uses the `init(_ elements: S)` initializer, 134 | // which performs unnecessary copying. 135 | let dummyPointer = UnsafeMutablePointer.allocate(capacity: 1) 136 | let scalarCount = shape.reduce(1, *) 137 | self.init(repeating: dummyPointer.move(), count: scalarCount) 138 | dummyPointer.deallocate() 139 | withUnsafeMutableBufferPointer { buffPtr in 140 | buffPtr.baseAddress!.update(from: ptr, count: scalarCount) 141 | } 142 | } 143 | } 144 | 145 | public extension Array where Element : NumpyScalarCompatible { 146 | /// Creates a 1-D `numpy.ndarray` instance with the same scalars as this 147 | /// `Array`. 148 | /// 149 | /// - Precondition: The `numpy` Python package must be installed. 150 | func makeNumpyArray() -> PythonObject { 151 | return withUnsafeBytes { bytes in 152 | let data = ctypes.cast(Int(bitPattern: bytes.baseAddress), 153 | ctypes.POINTER(Element.ctype)) 154 | let ndarray = np.ctypeslib.as_array(data, shape: PythonObject(tupleOf: count)) 155 | return np.copy(ndarray) 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /PythonKit/Python.swift: -------------------------------------------------------------------------------- 1 | //===-- Python.swift ------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift.org open source project 4 | // 5 | // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 | // 11 | //===----------------------------------------------------------------------===// 12 | // 13 | // This file defines an interoperability layer for talking to Python from Swift. 14 | // 15 | //===----------------------------------------------------------------------===// 16 | // 17 | // The model provided by this file is completely dynamic and does not require 18 | // invasive compiler support. 19 | // 20 | //===----------------------------------------------------------------------===// 21 | 22 | //===----------------------------------------------------------------------===// 23 | // `PyReference` definition 24 | //===----------------------------------------------------------------------===// 25 | 26 | /// Typealias used when passing or returning a `PyObject` pointer with 27 | /// implied ownership. 28 | @usableFromInline 29 | typealias OwnedPyObjectPointer = PyObjectPointer 30 | 31 | /// A primitive reference to a Python C API `PyObject`. 32 | /// 33 | /// A `PyReference` instance has ownership of its underlying `PyObject`, which 34 | /// must be non-null. 35 | /// 36 | // - Note: When Swift has ownership, `PyReference` should be removed. 37 | // `PythonObject` will define copy constructors, move constructors, etc. to 38 | // implement move semantics. 39 | @usableFromInline @_fixed_layout 40 | final class PyReference { 41 | private var pointer: OwnedPyObjectPointer 42 | 43 | // This `PyReference`, once deleted, will make no delta change to the 44 | // python object's reference count. It will however, retain the reference for 45 | // the lifespan of this object. 46 | init(_ pointer: OwnedPyObjectPointer) { 47 | self.pointer = pointer 48 | Py_IncRef(pointer) 49 | } 50 | 51 | // This `PyReference` adopts the +1 reference and will decrement it in the 52 | // future. 53 | init(consuming pointer: PyObjectPointer) { 54 | self.pointer = pointer 55 | } 56 | 57 | deinit { 58 | Py_DecRef(pointer) 59 | } 60 | 61 | var borrowedPyObject: PyObjectPointer { 62 | return pointer 63 | } 64 | 65 | var ownedPyObject: OwnedPyObjectPointer { 66 | Py_IncRef(pointer) 67 | return pointer 68 | } 69 | } 70 | 71 | //===----------------------------------------------------------------------===// 72 | // `PythonObject` definition 73 | //===----------------------------------------------------------------------===// 74 | 75 | // - Note: When Swift has ownership, `PythonObject` will define copy 76 | // constructors, move constructors, etc. to implement move semantics. 77 | 78 | /// `PythonObject` represents an object in Python and supports dynamic member 79 | /// lookup. Any member access like `object.foo` will dynamically request the 80 | /// Python runtime for a member with the specified name in this object. 81 | /// 82 | /// `PythonObject` is passed to and returned from all Python function calls and 83 | /// member references. It supports standard Python arithmetic and comparison 84 | /// operators. 85 | /// 86 | /// Internally, `PythonObject` is implemented as a reference-counted pointer to 87 | /// a Python C API `PyObject`. 88 | @dynamicCallable 89 | @dynamicMemberLookup 90 | public struct PythonObject { 91 | /// The underlying `PyReference`. 92 | fileprivate var reference: PyReference 93 | 94 | @usableFromInline 95 | init(_ pointer: PyReference) { 96 | reference = pointer 97 | } 98 | 99 | /// Creates a new instance and a new reference. 100 | init(_ pointer: OwnedPyObjectPointer) { 101 | reference = PyReference(pointer) 102 | } 103 | 104 | /// Creates a new instance consuming the specified `PyObject` pointer. 105 | init(consuming pointer: PyObjectPointer) { 106 | reference = PyReference(consuming: pointer) 107 | } 108 | 109 | fileprivate var borrowedPyObject: PyObjectPointer { 110 | return reference.borrowedPyObject 111 | } 112 | 113 | fileprivate var ownedPyObject: OwnedPyObjectPointer { 114 | return reference.ownedPyObject 115 | } 116 | } 117 | 118 | // Make `print(python)` print a pretty form of the `PythonObject`. 119 | extension PythonObject : CustomStringConvertible { 120 | /// A textual description of this `PythonObject`, produced by `Python.str`. 121 | public var description: String { 122 | // The `str` function is used here because it is designed to return 123 | // human-readable descriptions of Python objects. The Python REPL also uses 124 | // it for printing descriptions. 125 | // `repr` is not used because it is not designed to be readable and takes 126 | // too long for large objects. 127 | return String(Python.str(self))! 128 | } 129 | } 130 | 131 | // Make `PythonObject` show up nicely in the Xcode Playground results sidebar. 132 | extension PythonObject : CustomPlaygroundDisplayConvertible { 133 | public var playgroundDescription: Any { 134 | return description 135 | } 136 | } 137 | 138 | // Mirror representation, used by debugger/REPL. 139 | extension PythonObject : CustomReflectable { 140 | public var customMirror: Mirror { 141 | return Mirror(self, children: [], displayStyle: .struct) 142 | } 143 | } 144 | 145 | //===----------------------------------------------------------------------===// 146 | // `PythonConvertible` protocol 147 | //===----------------------------------------------------------------------===// 148 | 149 | /// A type whose values can be converted to a `PythonObject`. 150 | public protocol PythonConvertible { 151 | /// A `PythonObject` instance representing this value. 152 | var pythonObject: PythonObject { get } 153 | } 154 | 155 | public extension PythonObject { 156 | /// Creates a new instance from a `PythonConvertible` value. 157 | init(_ object: T) { 158 | self.init(object.pythonObject) 159 | } 160 | } 161 | 162 | /// Internal helpers to convert `PythonConvertible` values to owned and borrowed 163 | /// `PyObject` instances. These should not be made public. 164 | fileprivate extension PythonConvertible { 165 | var borrowedPyObject: PyObjectPointer { 166 | return pythonObject.borrowedPyObject 167 | } 168 | 169 | var ownedPyObject: OwnedPyObjectPointer { 170 | return pythonObject.ownedPyObject 171 | } 172 | } 173 | 174 | //===----------------------------------------------------------------------===// 175 | // `ConvertibleFromPython` protocol 176 | //===----------------------------------------------------------------------===// 177 | 178 | /// A type that can be initialized from a `PythonObject`. 179 | public protocol ConvertibleFromPython { 180 | /// Creates a new instance from the given `PythonObject`, if possible. 181 | /// - Note: Conversion may fail if the given `PythonObject` instance is 182 | /// incompatible (e.g. a Python `string` object cannot be converted into an 183 | /// `Int`). 184 | init?(_ object: PythonObject) 185 | } 186 | 187 | // `PythonObject` is trivially `PythonConvertible`. 188 | extension PythonObject : PythonConvertible, ConvertibleFromPython { 189 | public init(_ object: PythonObject) { 190 | self.init(consuming: object.ownedPyObject) 191 | } 192 | 193 | public var pythonObject: PythonObject { return self } 194 | } 195 | 196 | //===----------------------------------------------------------------------===// 197 | // `PythonObject` callable implementation 198 | //===----------------------------------------------------------------------===// 199 | 200 | public extension PythonObject { 201 | /// Returns a callable version of this `PythonObject`. When called, the result 202 | /// throws a Swift error if the underlying Python function throws a Python 203 | /// exception. 204 | var throwing: ThrowingPythonObject { 205 | return ThrowingPythonObject(self) 206 | } 207 | } 208 | 209 | /// An error produced by a failable Python operation. 210 | public enum PythonError : Error, Equatable { 211 | /// A Python runtime exception, produced by calling a Python function. 212 | case exception(PythonObject, traceback: PythonObject?) 213 | 214 | /// A failed call on a `PythonObject`. 215 | /// Reasons for failure include: 216 | /// - A non-callable Python object was called. 217 | /// - An incorrect number of arguments were provided to the callable Python 218 | /// object. 219 | /// - An invalid keyword argument was specified. 220 | case invalidCall(PythonObject) 221 | 222 | /// A module import error. 223 | case invalidModule(String) 224 | } 225 | 226 | extension PythonError : CustomStringConvertible { 227 | public var description: String { 228 | switch self { 229 | case .exception(let e, let t): 230 | var exceptionDescription = "Python exception: \(e)" 231 | if let t = t { 232 | let traceback = Python.import("traceback") 233 | exceptionDescription += """ 234 | \nTraceback: 235 | \(PythonObject("").join(traceback.format_tb(t))) 236 | """ 237 | } 238 | return exceptionDescription 239 | case .invalidCall(let e): 240 | return "Invalid Python call: \(e)" 241 | case .invalidModule(let m): 242 | return "Invalid Python module: \(m)" 243 | } 244 | } 245 | } 246 | 247 | // Reflect a Python error (which must be active) into a Swift error if one is 248 | // active. 249 | private func throwPythonErrorIfPresent() throws { 250 | if PyErr_Occurred() == nil { return } 251 | 252 | var type: PyObjectPointer? 253 | var value: PyObjectPointer? 254 | var traceback: PyObjectPointer? 255 | 256 | // Fetch the exception and clear the exception state. 257 | PyErr_Fetch(&type, &value, &traceback) 258 | 259 | // The value for the exception may not be set but the type always should be. 260 | let resultObject = PythonObject(consuming: value ?? type!) 261 | let tracebackObject = traceback.flatMap { PythonObject(consuming: $0) } 262 | throw PythonError.exception(resultObject, traceback: tracebackObject) 263 | } 264 | 265 | /// A `PythonObject` wrapper that enables throwing method calls. 266 | /// Exceptions produced by Python functions are reflected as Swift errors and 267 | /// thrown. 268 | /// - Note: It is intentional that `ThrowingPythonObject` does not have the 269 | /// `@dynamicCallable` attribute because the call syntax is unintuitive: 270 | /// `x.throwing(arg1, arg2, ...)`. The methods will still be named 271 | /// `dynamicallyCall` until further discussion/design. 272 | public struct ThrowingPythonObject { 273 | private var base: PythonObject 274 | 275 | fileprivate init(_ base: PythonObject) { 276 | self.base = base 277 | } 278 | 279 | /// Call `self` with the specified positional arguments. 280 | /// If the call fails for some reason, `PythonError.invalidCall` is thrown. 281 | /// - Precondition: `self` must be a Python callable. 282 | /// - Parameter args: Positional arguments for the Python callable. 283 | @discardableResult 284 | public func dynamicallyCall( 285 | withArguments args: PythonConvertible...) throws -> PythonObject { 286 | return try dynamicallyCall(withArguments: args) 287 | } 288 | 289 | /// Call `self` with the specified positional arguments. 290 | /// If the call fails for some reason, `PythonError.invalidCall` is thrown. 291 | /// - Precondition: `self` must be a Python callable. 292 | /// - Parameter args: Positional arguments for the Python callable. 293 | @discardableResult 294 | public func dynamicallyCall( 295 | withArguments args: [PythonConvertible] = []) throws -> PythonObject { 296 | try throwPythonErrorIfPresent() 297 | 298 | // Positional arguments are passed as a tuple of objects. 299 | let argTuple = pyTuple(args.map { $0.pythonObject }) 300 | defer { Py_DecRef(argTuple) } 301 | 302 | // Python calls always return a non-null object when successful. If the 303 | // Python function produces the equivalent of C `void`, it returns the 304 | // `None` object. A `null` result of `PyObjectCall` happens when there is an 305 | // error, like `self` not being a Python callable. 306 | let selfObject = base.ownedPyObject 307 | defer { Py_DecRef(selfObject) } 308 | 309 | guard let result = PyObject_CallObject(selfObject, argTuple) else { 310 | // If a Python exception was thrown, throw a corresponding Swift error. 311 | try throwPythonErrorIfPresent() 312 | throw PythonError.invalidCall(base) 313 | } 314 | return PythonObject(consuming: result) 315 | } 316 | 317 | /// Call `self` with the specified arguments. 318 | /// If the call fails for some reason, `PythonError.invalidCall` is thrown. 319 | /// - Precondition: `self` must be a Python callable. 320 | /// - Parameter args: Positional or keyword arguments for the Python callable. 321 | @discardableResult 322 | public func dynamicallyCall( 323 | withKeywordArguments args: 324 | KeyValuePairs = [:]) throws -> PythonObject { 325 | return try _dynamicallyCall(args) 326 | } 327 | 328 | /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal. 329 | /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it. 330 | @discardableResult 331 | public func dynamicallyCall( 332 | withKeywordArguments args: 333 | [(key: String, value: PythonConvertible)] = []) throws -> PythonObject { 334 | return try _dynamicallyCall(args) 335 | } 336 | 337 | /// Implementation of `dynamicallyCall(withKeywordArguments)`. 338 | private func _dynamicallyCall(_ args: T) throws -> PythonObject 339 | where T.Element == (key: String, value: PythonConvertible) { 340 | try throwPythonErrorIfPresent() 341 | 342 | // An array containing positional arguments. 343 | var positionalArgs: [PythonObject] = [] 344 | // A dictionary object for storing keyword arguments, if any exist. 345 | var kwdictObject: OwnedPyObjectPointer? = nil 346 | 347 | for (key, value) in args { 348 | if key.isEmpty { 349 | positionalArgs.append(value.pythonObject) 350 | continue 351 | } 352 | // Initialize dictionary object if necessary. 353 | if kwdictObject == nil { kwdictObject = PyDict_New()! } 354 | // Add key-value pair to the dictionary object. 355 | // TODO: Handle duplicate keys. 356 | // In Python, `SyntaxError: keyword argument repeated` is thrown. 357 | let k = PythonObject(key).ownedPyObject 358 | let v = value.ownedPyObject 359 | PyDict_SetItem(kwdictObject, k, v) 360 | Py_DecRef(k) 361 | Py_DecRef(v) 362 | } 363 | 364 | defer { Py_DecRef(kwdictObject) } // Py_DecRef is `nil` safe. 365 | 366 | // Positional arguments are passed as a tuple of objects. 367 | let argTuple = pyTuple(positionalArgs) 368 | defer { Py_DecRef(argTuple) } 369 | 370 | // Python calls always return a non-null object when successful. If the 371 | // Python function produces the equivalent of C `void`, it returns the 372 | // `None` object. A `null` result of `PyObjectCall` happens when there is an 373 | // error, like `self` not being a Python callable. 374 | let selfObject = base.ownedPyObject 375 | defer { Py_DecRef(selfObject) } 376 | 377 | guard let result = PyObject_Call(selfObject, argTuple, kwdictObject) else { 378 | // If a Python exception was thrown, throw a corresponding Swift error. 379 | try throwPythonErrorIfPresent() 380 | throw PythonError.invalidCall(base) 381 | } 382 | return PythonObject(consuming: result) 383 | } 384 | 385 | /// Converts to a 2-tuple, if possible. 386 | public var tuple2: (PythonObject, PythonObject)? { 387 | let ct = base.checking 388 | guard let elt0 = ct[0], let elt1 = ct[1] else { 389 | return nil 390 | } 391 | return (elt0, elt1) 392 | } 393 | 394 | /// Converts to a 3-tuple, if possible. 395 | public var tuple3: (PythonObject, PythonObject, PythonObject)? { 396 | let ct = base.checking 397 | guard let elt0 = ct[0], let elt1 = ct[1], let elt2 = ct[2] else { 398 | return nil 399 | } 400 | return (elt0, elt1, elt2) 401 | } 402 | 403 | /// Converts to a 4-tuple, if possible. 404 | public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? { 405 | let ct = base.checking 406 | guard let elt0 = ct[0], let elt1 = ct[1], 407 | let elt2 = ct[2], let elt3 = ct[3] else { 408 | return nil 409 | } 410 | return (elt0, elt1, elt2, elt3) 411 | } 412 | 413 | public var count: Int? { 414 | base.checking.count 415 | } 416 | } 417 | 418 | 419 | //===----------------------------------------------------------------------===// 420 | // `PythonObject` member access implementation 421 | //===----------------------------------------------------------------------===// 422 | 423 | public extension PythonObject { 424 | /// Returns a `PythonObject` wrapper capable of member accesses. 425 | var checking: CheckingPythonObject { 426 | return CheckingPythonObject(self) 427 | } 428 | } 429 | 430 | /// A `PythonObject` wrapper that enables member accesses. 431 | /// Member access operations return an `Optional` result. When member access 432 | /// fails, `nil` is returned. 433 | @dynamicMemberLookup 434 | public struct CheckingPythonObject { 435 | /// The underlying `PythonObject`. 436 | private var base: PythonObject 437 | 438 | fileprivate init(_ base: PythonObject) { 439 | self.base = base 440 | } 441 | 442 | public subscript(dynamicMember name: String) -> PythonObject? { 443 | get { 444 | let selfObject = base.ownedPyObject 445 | defer { Py_DecRef(selfObject) } 446 | guard let result = PyObject_GetAttrString(selfObject, name) else { 447 | PyErr_Clear() 448 | return nil 449 | } 450 | // `PyObject_GetAttrString` returns +1 result. 451 | return PythonObject(consuming: result) 452 | } 453 | } 454 | 455 | /// Access the element corresponding to the specified `PythonConvertible` 456 | /// values representing a key. 457 | /// - Note: This is equivalent to `object[key]` in Python. 458 | public subscript(key: [PythonConvertible]) -> PythonObject? { 459 | get { 460 | let keyObject = flattenedSubscriptIndices(key) 461 | let selfObject = base.ownedPyObject 462 | defer { 463 | Py_DecRef(keyObject) 464 | Py_DecRef(selfObject) 465 | } 466 | 467 | // `PyObject_GetItem` returns +1 reference. 468 | if let result = PyObject_GetItem(selfObject, keyObject) { 469 | return PythonObject(consuming: result) 470 | } 471 | PyErr_Clear() 472 | return nil 473 | } 474 | nonmutating set { 475 | let keyObject = flattenedSubscriptIndices(key) 476 | let selfObject = base.ownedPyObject 477 | defer { 478 | Py_DecRef(keyObject) 479 | Py_DecRef(selfObject) 480 | } 481 | 482 | if let newValue = newValue { 483 | let newValueObject = newValue.ownedPyObject 484 | PyObject_SetItem(selfObject, keyObject, newValueObject) 485 | Py_DecRef(newValueObject) 486 | } else { 487 | // Assigning `nil` deletes the key, just like Swift dictionaries. 488 | PyObject_DelItem(selfObject, keyObject) 489 | } 490 | } 491 | } 492 | 493 | /// Access the element corresponding to the specified `PythonConvertible` 494 | /// values representing a key. 495 | /// - Note: This is equivalent to `object[key]` in Python. 496 | public subscript(key: PythonConvertible...) -> PythonObject? { 497 | get { 498 | return self[key] 499 | } 500 | nonmutating set { 501 | self[key] = newValue 502 | } 503 | } 504 | 505 | /// Converts to a 2-tuple, if possible. 506 | public var tuple2: (PythonObject, PythonObject)? { 507 | guard let elt0 = self[0], let elt1 = self[1] else { 508 | return nil 509 | } 510 | return (elt0, elt1) 511 | } 512 | 513 | /// Converts to a 3-tuple, if possible. 514 | public var tuple3: (PythonObject, PythonObject, PythonObject)? { 515 | guard let elt0 = self[0], let elt1 = self[1], let elt2 = self[2] else { 516 | return nil 517 | } 518 | return (elt0, elt1, elt2) 519 | } 520 | 521 | /// Converts to a 4-tuple, if possible. 522 | public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? { 523 | guard let elt0 = self[0], let elt1 = self[1], 524 | let elt2 = self[2], let elt3 = self[3] else { 525 | return nil 526 | } 527 | return (elt0, elt1, elt2, elt3) 528 | } 529 | 530 | public var count: Int? { 531 | Int(Python.len(base)) 532 | } 533 | } 534 | 535 | //===----------------------------------------------------------------------===// 536 | // Core `PythonObject` API 537 | //===----------------------------------------------------------------------===// 538 | 539 | /// Converts an array of indices into a `PythonObject` representing a flattened 540 | /// index. 541 | private func flattenedSubscriptIndices( 542 | _ indices: [PythonConvertible]) -> OwnedPyObjectPointer { 543 | if indices.count == 1 { 544 | return indices[0].ownedPyObject 545 | } 546 | return pyTuple(indices.map { $0.pythonObject }) 547 | } 548 | 549 | public extension PythonObject { 550 | subscript(dynamicMember memberName: String) -> PythonObject { 551 | get { 552 | guard let member = checking[dynamicMember: memberName] else { 553 | fatalError("Could not access PythonObject member '\(memberName)'") 554 | } 555 | return member 556 | } 557 | nonmutating set { 558 | let selfObject = ownedPyObject 559 | defer { Py_DecRef(selfObject) } 560 | let valueObject = newValue.ownedPyObject 561 | defer { Py_DecRef(valueObject) } 562 | 563 | if PyObject_SetAttrString(selfObject, memberName, valueObject) == -1 { 564 | try! throwPythonErrorIfPresent() 565 | fatalError(""" 566 | Could not set PythonObject member '\(memberName)' to the specified \ 567 | value 568 | """) 569 | } 570 | } 571 | } 572 | 573 | /// Access the element corresponding to the specified `PythonConvertible` 574 | /// values representing a key. 575 | /// - Note: This is equivalent to `object[key]` in Python. 576 | subscript(key: PythonConvertible...) -> PythonObject { 577 | get { 578 | guard let item = checking[key] else { 579 | fatalError(""" 580 | Could not access PythonObject element corresponding to the specified \ 581 | key values: \(key) 582 | """) 583 | } 584 | return item 585 | } 586 | nonmutating set { 587 | checking[key] = newValue 588 | } 589 | } 590 | 591 | /// Converts to a 2-tuple. 592 | var tuple2: (PythonObject, PythonObject) { 593 | guard let result = checking.tuple2 else { 594 | fatalError("Could not convert PythonObject to a 2-element tuple") 595 | } 596 | return result 597 | } 598 | 599 | /// Converts to a 3-tuple. 600 | var tuple3: (PythonObject, PythonObject, PythonObject) { 601 | guard let result = checking.tuple3 else { 602 | fatalError("Could not convert PythonObject to a 3-element tuple") 603 | } 604 | return result 605 | } 606 | 607 | /// Converts to a 4-tuple. 608 | var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject) { 609 | guard let result = checking.tuple4 else { 610 | fatalError("Could not convert PythonObject to a 4-element tuple") 611 | } 612 | return result 613 | } 614 | 615 | /// Call `self` with the specified positional arguments. 616 | /// - Precondition: `self` must be a Python callable. 617 | /// - Parameter args: Positional arguments for the Python callable. 618 | @discardableResult 619 | func dynamicallyCall( 620 | withArguments args: [PythonConvertible] = []) -> PythonObject { 621 | return try! throwing.dynamicallyCall(withArguments: args) 622 | } 623 | 624 | /// Call `self` with the specified arguments. 625 | /// - Precondition: `self` must be a Python callable. 626 | /// - Parameter args: Positional or keyword arguments for the Python callable. 627 | @discardableResult 628 | func dynamicallyCall( 629 | withKeywordArguments args: 630 | KeyValuePairs = [:]) -> PythonObject { 631 | return try! throwing.dynamicallyCall(withKeywordArguments: args) 632 | } 633 | 634 | /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal. 635 | /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it. 636 | @discardableResult 637 | func dynamicallyCall( 638 | withKeywordArguments args: 639 | [(key: String, value: PythonConvertible)] = []) -> PythonObject { 640 | return try! throwing.dynamicallyCall(withKeywordArguments: args) 641 | } 642 | } 643 | 644 | //===----------------------------------------------------------------------===// 645 | // Python interface implementation 646 | //===----------------------------------------------------------------------===// 647 | 648 | /// The global Python interface. 649 | /// 650 | /// You can import Python modules and access Python builtin types and functions 651 | /// via the `Python` global variable. 652 | /// 653 | /// import Python 654 | /// // Import modules. 655 | /// let os = Python.import("os") 656 | /// let np = Python.import("numpy") 657 | /// 658 | /// // Use builtin types and functions. 659 | /// let list: PythonObject = [1, 2, 3] 660 | /// print(Python.len(list)) // Prints 3. 661 | /// print(Python.type(list) == Python.list) // Prints true. 662 | @_fixed_layout 663 | public let Python = PythonInterface() 664 | 665 | /// An interface for Python. 666 | /// 667 | /// `PythonInterface` allows interaction with Python. It can be used to import 668 | /// modules and dynamically access Python builtin types and functions. 669 | /// - Note: It is not intended for `PythonInterface` to be initialized 670 | /// directly. Instead, please use the global instance of `PythonInterface` 671 | /// called `Python`. 672 | @dynamicMemberLookup 673 | public struct PythonInterface { 674 | /// A dictionary of the Python builtins. 675 | public let builtins: PythonObject 676 | 677 | init() { 678 | Py_Initialize() // Initialize Python 679 | builtins = PythonObject(PyEval_GetBuiltins()) 680 | 681 | // Runtime Fixes: 682 | PyRun_SimpleString(""" 683 | import sys 684 | import os 685 | 686 | # Some Python modules expect to have at least one argument in `sys.argv`: 687 | sys.argv = [""] 688 | 689 | # Some Python modules require `sys.executable` to return the path 690 | # to the Python interpreter executable. In Darwin, Python 3 returns the 691 | # main process executable path instead: 692 | if sys.version_info.major == 3 and sys.platform == "darwin": 693 | executable_name = "python{}.{}".format(sys.version_info.major, sys.version_info.minor) 694 | sys.executable = os.path.join(sys.exec_prefix, "bin", executable_name) 695 | """) 696 | } 697 | 698 | public func attemptImport(_ name: String) throws -> PythonObject { 699 | guard let module = PyImport_ImportModule(name) else { 700 | try throwPythonErrorIfPresent() 701 | throw PythonError.invalidModule(name) 702 | } 703 | return PythonObject(consuming: module) 704 | } 705 | 706 | public func `import`(_ name: String) -> PythonObject { 707 | return try! attemptImport(name) 708 | } 709 | 710 | public subscript(dynamicMember name: String) -> PythonObject { 711 | return builtins[name] 712 | } 713 | 714 | // The Python runtime version. 715 | // Equivalent to `sys.version` in Python. 716 | public var version: PythonObject { 717 | return self.import("sys").version 718 | } 719 | 720 | // The Python runtime version information. 721 | // Equivalent to `sys.version_info` in Python. 722 | public var versionInfo: PythonObject { 723 | return self.import("sys").version_info 724 | } 725 | 726 | /// Emulates a Python `with` statement. 727 | /// - Parameter object: A context manager object. 728 | /// - Parameter body: A closure to call on the result of `object.__enter__()`. 729 | public func with(_ object: PythonObject, _ body: (PythonObject) throws -> Void) rethrows { 730 | let yieldValue = object.__enter__() 731 | try body(yieldValue) 732 | yieldValue.__exit__() 733 | } 734 | } 735 | 736 | //===----------------------------------------------------------------------===// 737 | // Helpers for Python tuple types 738 | //===----------------------------------------------------------------------===// 739 | 740 | // Create a Python tuple object with the specified elements. 741 | private func pyTuple(_ vals: T) -> OwnedPyObjectPointer 742 | where T.Element : PythonConvertible { 743 | let tuple = PyTuple_New(vals.count)! 744 | for (index, element) in vals.enumerated() { 745 | // `PyTuple_SetItem` steals the reference of the object stored. 746 | PyTuple_SetItem(tuple, index, element.ownedPyObject) 747 | } 748 | return tuple 749 | } 750 | 751 | public extension PythonObject { 752 | // Tuples require explicit support because tuple types cannot conform to 753 | // protocols. 754 | init(tupleOf elements: PythonConvertible...) { 755 | self.init(tupleContentsOf: elements) 756 | } 757 | 758 | init(tupleContentsOf elements: T) 759 | where T.Element == PythonConvertible { 760 | self.init(consuming: pyTuple(elements.map { $0.pythonObject })) 761 | } 762 | 763 | init(tupleContentsOf elements: T) 764 | where T.Element : PythonConvertible { 765 | self.init(consuming: pyTuple(elements)) 766 | } 767 | } 768 | 769 | //===----------------------------------------------------------------------===// 770 | // `PythonConvertible` conformance for basic Swift types 771 | //===----------------------------------------------------------------------===// 772 | 773 | /// Return true if the specified objects an instance of the low-level Python 774 | /// type descriptor passed in as 'type'. 775 | private func isType(_ object: PythonObject, 776 | type: PyObjectPointer) -> Bool { 777 | let typePyRef = PythonObject(type) 778 | 779 | let result = Python.isinstance(object, typePyRef) 780 | 781 | // We cannot use the normal failable Bool initializer from `PythonObject` 782 | // here because would cause an infinite loop. 783 | let pyObject = result.ownedPyObject 784 | defer { Py_DecRef(pyObject) } 785 | 786 | // Anything not equal to `Py_ZeroStruct` is truthy. 787 | return pyObject != _Py_ZeroStruct 788 | } 789 | 790 | extension Bool : PythonConvertible, ConvertibleFromPython { 791 | public init?(_ pythonObject: PythonObject) { 792 | guard isType(pythonObject, type: PyBool_Type) else { return nil } 793 | 794 | let pyObject = pythonObject.ownedPyObject 795 | defer { Py_DecRef(pyObject) } 796 | 797 | self = pyObject == _Py_TrueStruct 798 | } 799 | 800 | public var pythonObject: PythonObject { 801 | _ = Python // Ensure Python is initialized. 802 | return PythonObject(consuming: PyBool_FromLong(self ? 1 : 0)) 803 | } 804 | } 805 | 806 | extension String : PythonConvertible, ConvertibleFromPython { 807 | public init?(_ pythonObject: PythonObject) { 808 | let pyObject = pythonObject.ownedPyObject 809 | defer { Py_DecRef(pyObject) } 810 | 811 | guard let cString = PyString_AsString(pyObject) else { 812 | PyErr_Clear() 813 | return nil 814 | } 815 | self.init(cString: cString) 816 | } 817 | 818 | public var pythonObject: PythonObject { 819 | _ = Python // Ensure Python is initialized. 820 | let v = utf8CString.withUnsafeBufferPointer { 821 | // 1 is subtracted from the C string length to trim the trailing null 822 | // character (`\0`). 823 | PyString_FromStringAndSize($0.baseAddress, $0.count - 1)! 824 | } 825 | return PythonObject(consuming: v) 826 | } 827 | } 828 | 829 | fileprivate extension PythonObject { 830 | // Converts a `PythonObject` to the given type by applying the appropriate 831 | // converter function and checking the error value. 832 | func converted( 833 | withError errorValue: T, by converter: (OwnedPyObjectPointer) -> T 834 | ) -> T? { 835 | let pyObject = ownedPyObject 836 | defer { Py_DecRef(pyObject) } 837 | 838 | assert(PyErr_Occurred() == nil, 839 | "Python error occurred somewhere but wasn't handled") 840 | 841 | let value = converter(pyObject) 842 | guard value != errorValue || PyErr_Occurred() == nil else { 843 | PyErr_Clear() 844 | return nil 845 | } 846 | return value 847 | 848 | } 849 | } 850 | 851 | extension Int : PythonConvertible, ConvertibleFromPython { 852 | public init?(_ pythonObject: PythonObject) { 853 | // `PyInt_AsLong` return -1 and sets an error if the Python object is not 854 | // integer compatible. 855 | guard let value = pythonObject.converted( 856 | withError: -1, by: PyInt_AsLong) else { 857 | return nil 858 | } 859 | self = value 860 | } 861 | 862 | public var pythonObject: PythonObject { 863 | _ = Python // Ensure Python is initialized. 864 | return PythonObject(consuming: PyInt_FromLong(self)) 865 | } 866 | } 867 | 868 | extension UInt : PythonConvertible, ConvertibleFromPython { 869 | public init?(_ pythonObject: PythonObject) { 870 | // `PyInt_AsUnsignedLongMask` isn't documented as such, but in fact it does 871 | // return -1 and set an error if the Python object is not integer 872 | // compatible. 873 | guard let value = pythonObject.converted( 874 | withError: ~0, by: PyInt_AsUnsignedLongMask) else { 875 | return nil 876 | } 877 | self = value 878 | } 879 | 880 | public var pythonObject: PythonObject { 881 | _ = Python // Ensure Python is initialized. 882 | return PythonObject(consuming: PyInt_FromSize_t(self)) 883 | } 884 | } 885 | 886 | extension Double : PythonConvertible, ConvertibleFromPython { 887 | public init?(_ pythonObject: PythonObject) { 888 | // `PyFloat_AsDouble` return -1 and sets an error if the Python object is 889 | // not float compatible. 890 | guard let value = pythonObject.converted( 891 | withError: -1, by: PyFloat_AsDouble) else { 892 | return nil 893 | } 894 | self = value 895 | } 896 | 897 | public var pythonObject: PythonObject { 898 | _ = Python // Ensure Python is initialized. 899 | return PythonObject(consuming: PyFloat_FromDouble(self)) 900 | } 901 | } 902 | 903 | //===----------------------------------------------------------------------===// 904 | // `PythonConvertible` conformances for `FixedWidthInteger` and `Float` 905 | //===----------------------------------------------------------------------===// 906 | 907 | // Any `FixedWidthInteger` type is `PythonConvertible` via the `Int`/`UInt` 908 | // implementation. 909 | 910 | extension Int8 : PythonConvertible, ConvertibleFromPython { 911 | public init?(_ pythonObject: PythonObject) { 912 | guard let i = Int(pythonObject) else { return nil } 913 | self.init(i) 914 | } 915 | 916 | public var pythonObject: PythonObject { 917 | return Int(self).pythonObject 918 | } 919 | } 920 | 921 | extension Int16 : PythonConvertible, ConvertibleFromPython { 922 | public init?(_ pythonObject: PythonObject) { 923 | guard let i = Int(pythonObject) else { return nil } 924 | self.init(i) 925 | } 926 | 927 | public var pythonObject: PythonObject { 928 | return Int(self).pythonObject 929 | } 930 | } 931 | 932 | extension Int32 : PythonConvertible, ConvertibleFromPython { 933 | public init?(_ pythonObject: PythonObject) { 934 | guard let i = Int(pythonObject) else { return nil } 935 | self.init(i) 936 | } 937 | 938 | public var pythonObject: PythonObject { 939 | return Int(self).pythonObject 940 | } 941 | } 942 | 943 | extension Int64 : PythonConvertible, ConvertibleFromPython { 944 | public init?(_ pythonObject: PythonObject) { 945 | guard let i = Int(pythonObject) else { return nil } 946 | self.init(i) 947 | } 948 | 949 | public var pythonObject: PythonObject { 950 | return Int(self).pythonObject 951 | } 952 | } 953 | 954 | extension UInt8 : PythonConvertible, ConvertibleFromPython { 955 | public init?(_ pythonObject: PythonObject) { 956 | guard let i = UInt(pythonObject) else { return nil } 957 | self.init(i) 958 | } 959 | 960 | public var pythonObject: PythonObject { 961 | return UInt(self).pythonObject 962 | } 963 | } 964 | 965 | extension UInt16 : PythonConvertible, ConvertibleFromPython { 966 | public init?(_ pythonObject: PythonObject) { 967 | guard let i = UInt(pythonObject) else { return nil } 968 | self.init(i) 969 | } 970 | 971 | public var pythonObject: PythonObject { 972 | return UInt(self).pythonObject 973 | } 974 | } 975 | 976 | extension UInt32 : PythonConvertible, ConvertibleFromPython { 977 | public init?(_ pythonObject: PythonObject) { 978 | guard let i = UInt(pythonObject) else { return nil } 979 | self.init(i) 980 | } 981 | 982 | public var pythonObject: PythonObject { 983 | return UInt(self).pythonObject 984 | } 985 | } 986 | 987 | extension UInt64 : PythonConvertible, ConvertibleFromPython { 988 | public init?(_ pythonObject: PythonObject) { 989 | guard let i = UInt(pythonObject) else { return nil } 990 | self.init(i) 991 | } 992 | 993 | public var pythonObject: PythonObject { 994 | return UInt(self).pythonObject 995 | } 996 | } 997 | 998 | // `Float` is `PythonConvertible` via the `Double` implementation. 999 | 1000 | extension Float : PythonConvertible, ConvertibleFromPython { 1001 | public init?(_ pythonObject: PythonObject) { 1002 | guard let v = Double(pythonObject) else { return nil } 1003 | self.init(v) 1004 | } 1005 | 1006 | public var pythonObject: PythonObject { 1007 | return Double(self).pythonObject 1008 | } 1009 | } 1010 | 1011 | //===----------------------------------------------------------------------===// 1012 | // `PythonConvertible` conformance for `Optional` 1013 | //===----------------------------------------------------------------------===// 1014 | 1015 | extension Optional : PythonConvertible where Wrapped : PythonConvertible { 1016 | public var pythonObject: PythonObject { 1017 | return self?.pythonObject ?? Python.None 1018 | } 1019 | } 1020 | 1021 | //===----------------------------------------------------------------------===// 1022 | // `ConvertibleFromPython` conformance for `Optional` 1023 | //===----------------------------------------------------------------------===// 1024 | 1025 | extension Optional : ConvertibleFromPython 1026 | where Wrapped : ConvertibleFromPython { 1027 | public init?(_ object: PythonObject) { 1028 | if object == Python.None { 1029 | self = .none 1030 | } else { 1031 | guard let converted = Wrapped(object) else { 1032 | return nil 1033 | } 1034 | self = .some(converted) 1035 | } 1036 | } 1037 | } 1038 | 1039 | //===----------------------------------------------------------------------===// 1040 | // `PythonConvertible` and `ConvertibleFromPython conformance for 1041 | // `Array` and `Dictionary` 1042 | //===----------------------------------------------------------------------===// 1043 | 1044 | // `Array` conditionally conforms to `PythonConvertible` if the `Element` 1045 | // associated type does. 1046 | extension Array : PythonConvertible where Element : PythonConvertible { 1047 | public var pythonObject: PythonObject { 1048 | _ = Python // Ensure Python is initialized. 1049 | let list = PyList_New(count)! 1050 | for (index, element) in enumerated() { 1051 | // `PyList_SetItem` steals the reference of the object stored. 1052 | _ = PyList_SetItem(list, index, element.ownedPyObject) 1053 | } 1054 | return PythonObject(consuming: list) 1055 | } 1056 | } 1057 | 1058 | extension Array : ConvertibleFromPython where Element : ConvertibleFromPython { 1059 | public init?(_ pythonObject: PythonObject) { 1060 | self = [] 1061 | for elementObject in pythonObject { 1062 | guard let element = Element(elementObject) else { return nil } 1063 | append(element) 1064 | } 1065 | } 1066 | } 1067 | 1068 | // `Dictionary` conditionally conforms to `PythonConvertible` if the `Key` and 1069 | // `Value` associated types do. 1070 | extension Dictionary : PythonConvertible 1071 | where Key : PythonConvertible, Value : PythonConvertible { 1072 | public var pythonObject: PythonObject { 1073 | _ = Python // Ensure Python is initialized. 1074 | let dict = PyDict_New()! 1075 | for (key, value) in self { 1076 | let k = key.ownedPyObject 1077 | let v = value.ownedPyObject 1078 | PyDict_SetItem(dict, k, v) 1079 | Py_DecRef(k) 1080 | Py_DecRef(v) 1081 | } 1082 | return PythonObject(consuming: dict) 1083 | } 1084 | } 1085 | 1086 | extension Dictionary : ConvertibleFromPython 1087 | where Key : ConvertibleFromPython, Value : ConvertibleFromPython { 1088 | public init?(_ pythonDict: PythonObject) { 1089 | self = [:] 1090 | 1091 | // Iterate over the Python dictionary, converting its keys and values to 1092 | // Swift `Key` and `Value` pairs. 1093 | var key, value: PyObjectPointer? 1094 | var position: Int = 0 1095 | 1096 | while PyDict_Next( 1097 | pythonDict.borrowedPyObject, 1098 | &position, &key, &value) != 0 { 1099 | // If any key or value is not convertible to the corresponding Swift 1100 | // type, then the entire dictionary is not convertible. 1101 | if let swiftKey = Key(PythonObject(key!)), 1102 | let swiftValue = Value(PythonObject(value!)) { 1103 | // It is possible that there are duplicate keys after conversion. We 1104 | // silently allow duplicate keys and pick a nondeterministic result if 1105 | // there is a collision. 1106 | self[swiftKey] = swiftValue 1107 | } else { 1108 | return nil 1109 | } 1110 | } 1111 | } 1112 | } 1113 | 1114 | //===----------------------------------------------------------------------===// 1115 | // `PythonConvertible` and `ConvertibleFromPython` conformances 1116 | // for `Range` types 1117 | //===----------------------------------------------------------------------===// 1118 | 1119 | extension Range : PythonConvertible where Bound : PythonConvertible { 1120 | public var pythonObject: PythonObject { 1121 | _ = Python // Ensure Python is initialized. 1122 | return Python.slice(lowerBound, upperBound, Python.None) 1123 | } 1124 | } 1125 | 1126 | extension Range : ConvertibleFromPython where Bound : ConvertibleFromPython { 1127 | public init?(_ pythonObject: PythonObject) { 1128 | guard isType(pythonObject, type: PySlice_Type) else { return nil } 1129 | guard let lowerBound = Bound(pythonObject.start), 1130 | let upperBound = Bound(pythonObject.stop) else { 1131 | return nil 1132 | } 1133 | guard pythonObject.step == Python.None else { return nil } 1134 | self.init(uncheckedBounds: (lowerBound, upperBound)) 1135 | } 1136 | } 1137 | 1138 | extension PartialRangeFrom : PythonConvertible where Bound : PythonConvertible { 1139 | public var pythonObject: PythonObject { 1140 | _ = Python // Ensure Python is initialized. 1141 | return Python.slice(lowerBound, Python.None, Python.None) 1142 | } 1143 | } 1144 | 1145 | extension PartialRangeFrom : ConvertibleFromPython 1146 | where Bound : ConvertibleFromPython { 1147 | public init?(_ pythonObject: PythonObject) { 1148 | guard isType(pythonObject, type: PySlice_Type) else { return nil } 1149 | guard let lowerBound = Bound(pythonObject.start) else { return nil } 1150 | guard pythonObject.stop == Python.None, 1151 | pythonObject.step == Python.None else { 1152 | return nil 1153 | } 1154 | self.init(lowerBound) 1155 | } 1156 | } 1157 | 1158 | extension PartialRangeUpTo : PythonConvertible where Bound : PythonConvertible { 1159 | public var pythonObject: PythonObject { 1160 | _ = Python // Ensure Python is initialized. 1161 | return Python.slice(Python.None, upperBound, Python.None) 1162 | } 1163 | } 1164 | 1165 | extension PartialRangeUpTo : ConvertibleFromPython 1166 | where Bound : ConvertibleFromPython { 1167 | public init?(_ pythonObject: PythonObject) { 1168 | guard isType(pythonObject, type: PySlice_Type) else { return nil } 1169 | guard let upperBound = Bound(pythonObject.stop) else { return nil } 1170 | guard pythonObject.start == Python.None, 1171 | pythonObject.step == Python.None else { 1172 | return nil 1173 | } 1174 | self.init(upperBound) 1175 | } 1176 | } 1177 | 1178 | //===----------------------------------------------------------------------===// 1179 | // Standard operators and conformances 1180 | //===----------------------------------------------------------------------===// 1181 | 1182 | private typealias PythonBinaryOp = 1183 | (OwnedPyObjectPointer?, OwnedPyObjectPointer?) -> OwnedPyObjectPointer? 1184 | private typealias PythonUnaryOp = 1185 | (OwnedPyObjectPointer?) -> OwnedPyObjectPointer? 1186 | 1187 | private func performBinaryOp( 1188 | _ op: PythonBinaryOp, lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1189 | let result = op(lhs.borrowedPyObject, rhs.borrowedPyObject) 1190 | // If binary operation fails (e.g. due to `TypeError`), throw an exception. 1191 | try! throwPythonErrorIfPresent() 1192 | return PythonObject(consuming: result!) 1193 | } 1194 | 1195 | private func performUnaryOp( 1196 | _ op: PythonUnaryOp, operand: PythonObject) -> PythonObject { 1197 | let result = op(operand.borrowedPyObject) 1198 | // If unary operation fails (e.g. due to `TypeError`), throw an exception. 1199 | try! throwPythonErrorIfPresent() 1200 | return PythonObject(consuming: result!) 1201 | } 1202 | 1203 | public extension PythonObject { 1204 | static func + (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1205 | return performBinaryOp(PyNumber_Add, lhs: lhs, rhs: rhs) 1206 | } 1207 | 1208 | static func - (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1209 | return performBinaryOp(PyNumber_Subtract, lhs: lhs, rhs: rhs) 1210 | } 1211 | 1212 | static func * (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1213 | return performBinaryOp(PyNumber_Multiply, lhs: lhs, rhs: rhs) 1214 | } 1215 | 1216 | static func / (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1217 | return performBinaryOp(PyNumber_TrueDivide, lhs: lhs, rhs: rhs) 1218 | } 1219 | 1220 | static func += (lhs: inout PythonObject, rhs: PythonObject) { 1221 | lhs = performBinaryOp(PyNumber_InPlaceAdd, lhs: lhs, rhs: rhs) 1222 | } 1223 | 1224 | static func -= (lhs: inout PythonObject, rhs: PythonObject) { 1225 | lhs = performBinaryOp(PyNumber_InPlaceSubtract, lhs: lhs, rhs: rhs) 1226 | } 1227 | 1228 | static func *= (lhs: inout PythonObject, rhs: PythonObject) { 1229 | lhs = performBinaryOp(PyNumber_InPlaceMultiply, lhs: lhs, rhs: rhs) 1230 | } 1231 | 1232 | static func /= (lhs: inout PythonObject, rhs: PythonObject) { 1233 | lhs = performBinaryOp(PyNumber_InPlaceTrueDivide, lhs: lhs, rhs: rhs) 1234 | } 1235 | } 1236 | 1237 | public extension PythonObject { 1238 | static func & (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1239 | return performBinaryOp(PyNumber_And, lhs: lhs, rhs: rhs) 1240 | } 1241 | 1242 | static func | (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1243 | return performBinaryOp(PyNumber_Or, lhs: lhs, rhs: rhs) 1244 | } 1245 | 1246 | static func ^ (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1247 | return performBinaryOp(PyNumber_Xor, lhs: lhs, rhs: rhs) 1248 | } 1249 | 1250 | static func &= (lhs: inout PythonObject, rhs: PythonObject) { 1251 | lhs = performBinaryOp(PyNumber_InPlaceAnd, lhs: lhs, rhs: rhs) 1252 | } 1253 | 1254 | static func |= (lhs: inout PythonObject, rhs: PythonObject) { 1255 | lhs = performBinaryOp(PyNumber_InPlaceOr, lhs: lhs, rhs: rhs) 1256 | } 1257 | 1258 | static func ^= (lhs: inout PythonObject, rhs: PythonObject) { 1259 | lhs = performBinaryOp(PyNumber_InPlaceXor, lhs: lhs, rhs: rhs) 1260 | } 1261 | 1262 | static prefix func ~ (_ operand: Self) -> Self { 1263 | return performUnaryOp(PyNumber_Invert, operand: operand) 1264 | } 1265 | } 1266 | 1267 | extension PythonObject : SignedNumeric { 1268 | public init(exactly value: T) { 1269 | self.init(Int(value)) 1270 | } 1271 | 1272 | public typealias Magnitude = PythonObject 1273 | 1274 | public var magnitude: PythonObject { 1275 | return self < 0 ? -self : self 1276 | } 1277 | 1278 | // Override the default implementation of `-` prefix function 1279 | // from SignedNumeric (https://bugs.swift.org/browse/SR-13293). 1280 | public static prefix func - (_ operand: Self) -> Self { 1281 | return performUnaryOp(PyNumber_Negative, operand: operand) 1282 | } 1283 | } 1284 | 1285 | extension PythonObject : Strideable { 1286 | public typealias Stride = PythonObject 1287 | 1288 | public func distance(to other: PythonObject) -> Stride { 1289 | return other - self 1290 | } 1291 | 1292 | public func advanced(by stride: Stride) -> PythonObject { 1293 | return self + stride 1294 | } 1295 | } 1296 | 1297 | extension PythonObject : Equatable, Comparable { 1298 | // `Equatable` and `Comparable` are implemented using rich comparison. 1299 | // This is consistent with how Python handles comparisons. 1300 | private func compared(to other: PythonObject, byOp: Int32) -> Bool { 1301 | let lhsObject = ownedPyObject 1302 | let rhsObject = other.ownedPyObject 1303 | defer { 1304 | Py_DecRef(lhsObject) 1305 | Py_DecRef(rhsObject) 1306 | } 1307 | assert(PyErr_Occurred() == nil, 1308 | "Python error occurred somewhere but wasn't handled") 1309 | switch PyObject_RichCompareBool(lhsObject, rhsObject, byOp) { 1310 | case 0: return false 1311 | case 1: return true 1312 | default: 1313 | try! throwPythonErrorIfPresent() 1314 | fatalError("No result or error returned when comparing \(self) to \(other)") 1315 | } 1316 | } 1317 | 1318 | public static func == (lhs: PythonObject, rhs: PythonObject) -> Bool { 1319 | return lhs.compared(to: rhs, byOp: Py_EQ) 1320 | } 1321 | 1322 | public static func != (lhs: PythonObject, rhs: PythonObject) -> Bool { 1323 | return lhs.compared(to: rhs, byOp: Py_NE) 1324 | } 1325 | 1326 | public static func < (lhs: PythonObject, rhs: PythonObject) -> Bool { 1327 | return lhs.compared(to: rhs, byOp: Py_LT) 1328 | } 1329 | 1330 | public static func <= (lhs: PythonObject, rhs: PythonObject) -> Bool { 1331 | return lhs.compared(to: rhs, byOp: Py_LE) 1332 | } 1333 | 1334 | public static func > (lhs: PythonObject, rhs: PythonObject) -> Bool { 1335 | return lhs.compared(to: rhs, byOp: Py_GT) 1336 | } 1337 | 1338 | public static func >= (lhs: PythonObject, rhs: PythonObject) -> Bool { 1339 | return lhs.compared(to: rhs, byOp: Py_GE) 1340 | } 1341 | } 1342 | 1343 | public extension PythonObject { 1344 | private func compared(to other: PythonObject, byOp: Int32) -> PythonObject { 1345 | let lhsObject = ownedPyObject 1346 | let rhsObject = other.ownedPyObject 1347 | defer { 1348 | Py_DecRef(lhsObject) 1349 | Py_DecRef(rhsObject) 1350 | } 1351 | assert(PyErr_Occurred() == nil, 1352 | "Python error occurred somewhere but wasn't handled") 1353 | guard let result = PyObject_RichCompare(lhsObject, rhsObject, byOp) else { 1354 | // If a Python exception was thrown, throw a corresponding Swift error. 1355 | try! throwPythonErrorIfPresent() 1356 | fatalError("No result or error returned when comparing \(self) to \(other)") 1357 | } 1358 | return PythonObject(consuming: result) 1359 | } 1360 | 1361 | static func == (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1362 | return lhs.compared(to: rhs, byOp: Py_EQ) 1363 | } 1364 | 1365 | static func != (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1366 | return lhs.compared(to: rhs, byOp: Py_NE) 1367 | } 1368 | 1369 | static func < (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1370 | return lhs.compared(to: rhs, byOp: Py_LT) 1371 | } 1372 | 1373 | static func <= (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1374 | return lhs.compared(to: rhs, byOp: Py_LE) 1375 | } 1376 | 1377 | static func > (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1378 | return lhs.compared(to: rhs, byOp: Py_GT) 1379 | } 1380 | 1381 | static func >= (lhs: PythonObject, rhs: PythonObject) -> PythonObject { 1382 | return lhs.compared(to: rhs, byOp: Py_GE) 1383 | } 1384 | } 1385 | 1386 | extension PythonObject : Hashable { 1387 | public func hash(into hasher: inout Hasher) { 1388 | guard let hash = Int(self.__hash__()) else { 1389 | fatalError("Cannot use '__hash__' on \(self)") 1390 | } 1391 | hasher.combine(hash) 1392 | } 1393 | } 1394 | 1395 | extension PythonObject : MutableCollection { 1396 | public typealias Index = PythonObject 1397 | public typealias Element = PythonObject 1398 | 1399 | public var startIndex: Index { 1400 | return 0 1401 | } 1402 | 1403 | public var endIndex: Index { 1404 | return Python.len(self) 1405 | } 1406 | 1407 | public func index(after i: Index) -> Index { 1408 | return i + PythonObject(1) 1409 | } 1410 | 1411 | public subscript(index: PythonObject) -> PythonObject { 1412 | get { 1413 | return self[index as PythonConvertible] 1414 | } 1415 | set { 1416 | self[index as PythonConvertible] = newValue 1417 | } 1418 | } 1419 | } 1420 | 1421 | extension PythonObject : Sequence { 1422 | public struct Iterator : IteratorProtocol { 1423 | fileprivate let pythonIterator: PythonObject 1424 | 1425 | public func next() -> PythonObject? { 1426 | guard let result = PyIter_Next(self.pythonIterator.borrowedPyObject) else { 1427 | try! throwPythonErrorIfPresent() 1428 | return nil 1429 | } 1430 | return PythonObject(consuming: result) 1431 | } 1432 | } 1433 | 1434 | public func makeIterator() -> Iterator { 1435 | guard let result = PyObject_GetIter(borrowedPyObject) else { 1436 | try! throwPythonErrorIfPresent() 1437 | // Unreachable. A Python `TypeError` must have been thrown. 1438 | preconditionFailure() 1439 | } 1440 | return Iterator(pythonIterator: PythonObject(consuming: result)) 1441 | } 1442 | } 1443 | 1444 | extension PythonObject { 1445 | public var count: Int { 1446 | checking.count! 1447 | } 1448 | } 1449 | 1450 | //===----------------------------------------------------------------------===// 1451 | // `ExpressibleByLiteral` conformances 1452 | //===----------------------------------------------------------------------===// 1453 | 1454 | extension PythonObject : ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, 1455 | ExpressibleByFloatLiteral, ExpressibleByStringLiteral { 1456 | public init(booleanLiteral value: Bool) { 1457 | self.init(value) 1458 | } 1459 | public init(integerLiteral value: Int) { 1460 | self.init(value) 1461 | } 1462 | public init(floatLiteral value: Double) { 1463 | self.init(value) 1464 | } 1465 | public init(stringLiteral value: String) { 1466 | self.init(value) 1467 | } 1468 | } 1469 | 1470 | extension PythonObject : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral { 1471 | public init(arrayLiteral elements: PythonObject...) { 1472 | self.init(elements) 1473 | } 1474 | public typealias Key = PythonObject 1475 | public typealias Value = PythonObject 1476 | 1477 | // Preserves element order in the final Python object, unlike 1478 | // `Dictionary.pythonObject`. When keys are duplicated, throw the same 1479 | // runtime error as `Swift.Dictionary.init(dictionaryLiteral:)`. This 1480 | // differs from Python's key uniquing semantics, which silently override an 1481 | // existing key with the next one it encounters. 1482 | public init(dictionaryLiteral elements: (PythonObject, PythonObject)...) { 1483 | _ = Python // Ensure Python is initialized. 1484 | let dict = PyDict_New()! 1485 | for (key, value) in elements { 1486 | let k = key.ownedPyObject 1487 | let v = value.ownedPyObject 1488 | 1489 | // Use Python's native key checking instead of querying whether 1490 | // `elements` contains the key. Although this could theoretically 1491 | // produce different results, it produces the Python object we want. 1492 | switch PyDict_Contains(dict, k) { 1493 | case 0: 1494 | PyDict_SetItem(dict, k, v) 1495 | case 1: 1496 | fatalError("Dictionary literal contains duplicate keys") 1497 | default: 1498 | try! throwPythonErrorIfPresent() 1499 | fatalError("No result or error checking whether \(elements) contains \(key)") 1500 | } 1501 | 1502 | Py_DecRef(k) 1503 | Py_DecRef(v) 1504 | } 1505 | self.init(consuming: dict) 1506 | } 1507 | } 1508 | 1509 | public struct PythonBytes : PythonConvertible, ConvertibleFromPython, Hashable { 1510 | public private(set) var pythonObject: PythonObject 1511 | 1512 | public init?(_ pythonObject: PythonObject) { 1513 | // We try to get the string/size pointers out. If it works, hooray, this is a bytes 1514 | // otherwise it isn't. 1515 | let pyObject = pythonObject.ownedPyObject 1516 | defer { Py_DecRef(pyObject) } 1517 | 1518 | var length = 0 1519 | var buffer: UnsafeMutablePointer? = nil 1520 | 1521 | switch PyBytes_AsStringAndSize(pyObject, &buffer, &length) { 1522 | case 0: 1523 | self.pythonObject = pythonObject 1524 | default: 1525 | return nil 1526 | } 1527 | } 1528 | 1529 | @inlinable 1530 | public init(_ bytes: Bytes) where Bytes.Element == UInt8 { 1531 | let possibleSelf = bytes.withContiguousStorageIfAvailable { storagePtr in 1532 | PythonBytes.fromBytePointer(storagePtr) 1533 | } 1534 | if let actualSelf = possibleSelf { 1535 | self = actualSelf 1536 | } else { 1537 | let temporaryBuffer = Array(bytes) 1538 | self = temporaryBuffer.withUnsafeBufferPointer { 1539 | PythonBytes.fromBytePointer($0) 1540 | } 1541 | } 1542 | } 1543 | 1544 | @inlinable 1545 | public init(_ bytes: Bytes) where Bytes.Element == Int8 { 1546 | let possibleSelf = bytes.withContiguousStorageIfAvailable { storagePtr in 1547 | PythonBytes.fromBytePointer(storagePtr) 1548 | } 1549 | if let actualSelf = possibleSelf { 1550 | self = actualSelf 1551 | } else { 1552 | let temporaryBuffer = Array(bytes) 1553 | self = temporaryBuffer.withUnsafeBufferPointer { 1554 | PythonBytes.fromBytePointer($0) 1555 | } 1556 | } 1557 | } 1558 | 1559 | private init(bytesObject: PythonObject) { 1560 | self.pythonObject = bytesObject 1561 | } 1562 | 1563 | @usableFromInline 1564 | static func fromBytePointer(_ bytes: UnsafeBufferPointer) -> PythonBytes { 1565 | bytes.withMemoryRebound(to: Int8.self) { reboundPtr in 1566 | PythonBytes.fromBytePointer(reboundPtr) 1567 | } 1568 | } 1569 | 1570 | @usableFromInline 1571 | static func fromBytePointer(_ bytes: UnsafeBufferPointer) -> PythonBytes { 1572 | let v = PyBytes_FromStringAndSize(bytes.baseAddress, bytes.count)! 1573 | return PythonBytes(bytesObject: PythonObject(consuming: v)) 1574 | } 1575 | 1576 | public func withUnsafeBytes( 1577 | _ callback: (UnsafeRawBufferPointer) throws -> ReturnValue 1578 | ) rethrows -> ReturnValue { 1579 | let pyObject = self.pythonObject.ownedPyObject 1580 | defer { Py_DecRef(pyObject) } 1581 | 1582 | var length = 0 1583 | var buffer: UnsafeMutablePointer? = nil 1584 | 1585 | switch PyBytes_AsStringAndSize(pyObject, &buffer, &length) { 1586 | case 0: 1587 | let buffer = UnsafeRawBufferPointer(start: buffer, count: length) 1588 | return try callback(buffer) 1589 | default: 1590 | try! throwPythonErrorIfPresent() 1591 | fatalError("No result or error getting interior buffer for bytes \(self)") 1592 | } 1593 | } 1594 | } 1595 | 1596 | //===----------------------------------------------------------------------===// 1597 | // PythonFunction - create functions in Swift that can be called from Python 1598 | //===----------------------------------------------------------------------===// 1599 | 1600 | /// Create functions in Swift that can be called from Python 1601 | /// 1602 | /// Example: 1603 | /// 1604 | /// The Python code `map(lambda(x: x * 2), [10, 12, 14])` would be written as: 1605 | /// 1606 | /// Python.map(PythonFunction { x in x * 2 }, [10, 12, 14]) // [20, 24, 28] 1607 | /// 1608 | final class PyFunction { 1609 | enum CallingConvention { 1610 | case varArgs 1611 | case varArgsWithKeywords 1612 | } 1613 | 1614 | /// Allows `PyFunction` to store Python functions with more than one possible calling convention 1615 | var callingConvention: CallingConvention 1616 | 1617 | /// `arguments` is a Python tuple. 1618 | typealias VarArgsFunction = ( 1619 | _ arguments: PythonObject) throws -> PythonConvertible 1620 | 1621 | /// `arguments` is a Python tuple. 1622 | /// `keywordArguments` is an OrderedDict in Python 3.6 and later, or a dict otherwise. 1623 | typealias VarArgsWithKeywordsFunction = ( 1624 | _ arguments: PythonObject, 1625 | _ keywordArguments: PythonObject) throws -> PythonConvertible 1626 | 1627 | /// Has the same memory layout as any other function with the Swift calling convention 1628 | private typealias Storage = () throws -> PythonConvertible 1629 | 1630 | /// Stores all function pointers in the same stored property. `callAsFunction` casts this into the desired type. 1631 | private var callSwiftFunction: Storage 1632 | 1633 | init(_ callSwiftFunction: @escaping VarArgsFunction) { 1634 | self.callingConvention = .varArgs 1635 | self.callSwiftFunction = unsafeBitCast(callSwiftFunction, to: Storage.self) 1636 | } 1637 | 1638 | init(_ callSwiftFunction: @escaping VarArgsWithKeywordsFunction) { 1639 | self.callingConvention = .varArgsWithKeywords 1640 | self.callSwiftFunction = unsafeBitCast(callSwiftFunction, to: Storage.self) 1641 | } 1642 | 1643 | private func checkConvention(_ calledConvention: CallingConvention) { 1644 | precondition(callingConvention == calledConvention, 1645 | "Called PyFunction with convention \(calledConvention), but expected \(callingConvention)") 1646 | } 1647 | 1648 | func callAsFunction(_ argumentsTuple: PythonObject) throws -> PythonConvertible { 1649 | checkConvention(.varArgs) 1650 | let callSwiftFunction = unsafeBitCast(self.callSwiftFunction, to: VarArgsFunction.self) 1651 | return try callSwiftFunction(argumentsTuple) 1652 | } 1653 | 1654 | func callAsFunction(_ argumentsTuple: PythonObject, _ keywordArguments: PythonObject) throws -> PythonConvertible { 1655 | checkConvention(.varArgsWithKeywords) 1656 | let callSwiftFunction = unsafeBitCast(self.callSwiftFunction, to: VarArgsWithKeywordsFunction.self) 1657 | return try callSwiftFunction(argumentsTuple, keywordArguments) 1658 | } 1659 | } 1660 | 1661 | public struct PythonFunction { 1662 | /// Called directly by the Python C API 1663 | private var function: PyFunction 1664 | 1665 | @_disfavoredOverload 1666 | public init(_ fn: @escaping (PythonObject) throws -> PythonConvertible) { 1667 | function = PyFunction { argumentsAsTuple in 1668 | return try fn(argumentsAsTuple[0]) 1669 | } 1670 | } 1671 | 1672 | /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead. 1673 | public init(_ fn: @escaping ([PythonObject]) throws -> PythonConvertible) { 1674 | function = PyFunction { argumentsAsTuple in 1675 | return try fn(argumentsAsTuple.map { $0 }) 1676 | } 1677 | } 1678 | 1679 | /// For cases where the Swift function should accept keyword arguments as `**kwargs` in Python. 1680 | /// `**kwargs` must preserve order from Python 3.6 onward, similarly to 1681 | /// Swift `KeyValuePairs` and unlike `Dictionary`. `KeyValuePairs` cannot be 1682 | /// mutated, so the next best solution is to use `[KeyValuePairs.Element]`. 1683 | public init(_ fn: @escaping ([PythonObject], [(key: String, value: PythonObject)]) throws -> PythonConvertible) { 1684 | function = PyFunction { argumentsAsTuple, keywordArgumentsAsDictionary in 1685 | var kwargs: [(String, PythonObject)] = [] 1686 | for keyAndValue in keywordArgumentsAsDictionary.items() { 1687 | let (key, value) = keyAndValue.tuple2 1688 | kwargs.append((String(key)!, value)) 1689 | } 1690 | return try fn(argumentsAsTuple.map { $0 }, kwargs) 1691 | } 1692 | } 1693 | } 1694 | 1695 | extension PythonFunction : PythonConvertible { 1696 | public var pythonObject: PythonObject { 1697 | // Ensure Python is initialized, and check for version match. 1698 | let versionMajor = Python.versionInfo.major 1699 | let versionMinor = Python.versionInfo.minor 1700 | guard (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3 else { 1701 | fatalError("PythonFunction only supports Python 3.1 and above.") 1702 | } 1703 | 1704 | let destructor: @convention(c) (PyObjectPointer?) -> Void = { capsulePointer in 1705 | let funcPointer = PyCapsule_GetPointer(capsulePointer, nil) 1706 | Unmanaged.fromOpaque(funcPointer).release() 1707 | } 1708 | let funcPointer = Unmanaged.passRetained(function).toOpaque() 1709 | let capsulePointer = PyCapsule_New( 1710 | funcPointer, 1711 | nil, 1712 | unsafeBitCast(destructor, to: OpaquePointer.self) 1713 | ) 1714 | 1715 | var methodDefinition: UnsafeMutablePointer 1716 | switch function.callingConvention { 1717 | case .varArgs: 1718 | methodDefinition = PythonFunction.sharedMethodDefinition 1719 | case .varArgsWithKeywords: 1720 | methodDefinition = PythonFunction.sharedMethodWithKeywordsDefinition 1721 | } 1722 | let pyFuncPointer = PyCFunction_NewEx( 1723 | methodDefinition, 1724 | capsulePointer, 1725 | nil 1726 | ) 1727 | 1728 | return PythonObject(consuming: pyFuncPointer) 1729 | } 1730 | } 1731 | 1732 | fileprivate extension PythonFunction { 1733 | static let sharedMethodDefinition: UnsafeMutablePointer = { 1734 | let name: StaticString = "pythonkit_swift_function" 1735 | // `utf8Start` is a property of StaticString, thus, it has a stable pointer. 1736 | let namePointer = UnsafeRawPointer(name.utf8Start).assumingMemoryBound(to: Int8.self) 1737 | 1738 | let methodImplementationPointer = unsafeBitCast( 1739 | PythonFunction.sharedMethodImplementation, to: OpaquePointer.self) 1740 | 1741 | /// The standard calling convention. See Python C API docs 1742 | let METH_VARARGS = 0x0001 as Int32 1743 | 1744 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 1745 | pointer.pointee = PyMethodDef( 1746 | ml_name: namePointer, 1747 | ml_meth: methodImplementationPointer, 1748 | ml_flags: METH_VARARGS, 1749 | ml_doc: nil 1750 | ) 1751 | 1752 | return pointer 1753 | }() 1754 | 1755 | static let sharedMethodWithKeywordsDefinition: UnsafeMutablePointer = { 1756 | let name: StaticString = "pythonkit_swift_function_with_keywords" 1757 | // `utf8Start` is a property of StaticString, thus, it has a stable pointer. 1758 | let namePointer = UnsafeRawPointer(name.utf8Start).assumingMemoryBound(to: Int8.self) 1759 | 1760 | let methodImplementationPointer = unsafeBitCast( 1761 | PythonFunction.sharedMethodWithKeywordsImplementation, to: OpaquePointer.self) 1762 | 1763 | /// A combination of flags that supports `**kwargs`. See Python C API docs 1764 | let METH_VARARGS = 0x0001 as Int32 1765 | let METH_KEYWORDS = 0x0002 as Int32 1766 | 1767 | let pointer = UnsafeMutablePointer.allocate(capacity: 1) 1768 | pointer.pointee = PyMethodDef( 1769 | ml_name: namePointer, 1770 | ml_meth: methodImplementationPointer, 1771 | ml_flags: METH_VARARGS | METH_KEYWORDS, 1772 | ml_doc: nil 1773 | ) 1774 | 1775 | return pointer 1776 | }() 1777 | 1778 | private static let sharedMethodImplementation: @convention(c) ( 1779 | PyObjectPointer?, PyObjectPointer? 1780 | ) -> PyObjectPointer? = { context, argumentsPointer in 1781 | guard let argumentsPointer = argumentsPointer, let capsulePointer = context else { 1782 | return nil 1783 | } 1784 | 1785 | let funcPointer = PyCapsule_GetPointer(capsulePointer, nil) 1786 | let function = Unmanaged.fromOpaque(funcPointer).takeUnretainedValue() 1787 | 1788 | do { 1789 | let argumentsAsTuple = PythonObject(consuming: argumentsPointer) 1790 | return try function(argumentsAsTuple).ownedPyObject 1791 | } catch { 1792 | PythonFunction.setPythonError(swiftError: error) 1793 | return nil // This must only be `nil` if an exception has been set 1794 | } 1795 | } 1796 | 1797 | private static let sharedMethodWithKeywordsImplementation: @convention(c) ( 1798 | PyObjectPointer?, PyObjectPointer?, PyObjectPointer? 1799 | ) -> PyObjectPointer? = { context, argumentsPointer, keywordArgumentsPointer in 1800 | guard let argumentsPointer = argumentsPointer, let capsulePointer = context else { 1801 | return nil 1802 | } 1803 | 1804 | let funcPointer = PyCapsule_GetPointer(capsulePointer, nil) 1805 | let function = Unmanaged.fromOpaque(funcPointer).takeUnretainedValue() 1806 | 1807 | do { 1808 | let argumentsAsTuple = PythonObject(consuming: argumentsPointer) 1809 | var keywordArgumentsAsDictionary: PythonObject 1810 | if let keywordArgumentsPointer = keywordArgumentsPointer { 1811 | keywordArgumentsAsDictionary = PythonObject(consuming: keywordArgumentsPointer) 1812 | } else { 1813 | keywordArgumentsAsDictionary = [:] 1814 | } 1815 | return try function(argumentsAsTuple, keywordArgumentsAsDictionary).ownedPyObject 1816 | } catch { 1817 | PythonFunction.setPythonError(swiftError: error) 1818 | return nil // This must only be `nil` if an exception has been set 1819 | } 1820 | } 1821 | 1822 | private static func setPythonError(swiftError: Error) { 1823 | if let pythonObject = swiftError as? PythonObject { 1824 | if Bool(Python.isinstance(pythonObject, Python.BaseException))! { 1825 | // We are an instance of an Exception class type. Set the exception class to the object's type: 1826 | PyErr_SetString(Python.type(pythonObject).ownedPyObject, pythonObject.description) 1827 | } else { 1828 | // Assume an actual class type was thrown (rather than an instance) 1829 | // Crashes if it was neither a subclass of BaseException nor an instance of one. 1830 | // 1831 | // We *could* check to see whether `pythonObject` is a class here and fall back 1832 | // to the default case of setting a generic Exception, below, but we also want 1833 | // people to write valid code. 1834 | PyErr_SetString(pythonObject.ownedPyObject, pythonObject.description) 1835 | } 1836 | } else { 1837 | // Make a generic Python Exception based on the Swift Error: 1838 | PyErr_SetString(Python.Exception.ownedPyObject, "\(type(of: swiftError)) raised in Swift: \(swiftError)") 1839 | } 1840 | } 1841 | } 1842 | 1843 | extension PythonObject: Error {} 1844 | 1845 | // From Python's C Headers: 1846 | struct PyMethodDef { 1847 | /// The name of the built-in function/method 1848 | var ml_name: UnsafePointer 1849 | 1850 | /// The C function that implements it. 1851 | /// Since this accepts multiple function signatures, the Swift type must be opaque here. 1852 | var ml_meth: OpaquePointer 1853 | 1854 | /// Combination of METH_xxx flags, which mostly describe the args expected by the C func 1855 | var ml_flags: Int32 1856 | 1857 | /// The __doc__ attribute, or NULL 1858 | var ml_doc: UnsafePointer? 1859 | } 1860 | 1861 | //===----------------------------------------------------------------------===// 1862 | // PythonInstanceMethod - create functions that can be bound to a Python object 1863 | //===----------------------------------------------------------------------===// 1864 | 1865 | public struct PythonInstanceMethod { 1866 | private var function: PythonFunction 1867 | 1868 | @_disfavoredOverload 1869 | public init(_ fn: @escaping (PythonObject) throws -> PythonConvertible) { 1870 | function = PythonFunction(fn) 1871 | } 1872 | 1873 | public init(_ fn: @escaping ([PythonObject]) throws -> PythonConvertible) { 1874 | function = PythonFunction(fn) 1875 | } 1876 | 1877 | public init(_ fn: @escaping ([PythonObject], [(key: String, value: PythonObject)]) throws -> PythonConvertible) { 1878 | function = PythonFunction(fn) 1879 | } 1880 | } 1881 | 1882 | extension PythonInstanceMethod : PythonConvertible { 1883 | public var pythonObject: PythonObject { 1884 | let pyFuncPointer = function.pythonObject.ownedPyObject 1885 | let methodPointer = PyInstanceMethod_New(pyFuncPointer) 1886 | return PythonObject(consuming: methodPointer) 1887 | } 1888 | } 1889 | 1890 | //===----------------------------------------------------------------------===// 1891 | // PythonClass - construct subclasses of a Python class 1892 | //===----------------------------------------------------------------------===// 1893 | 1894 | public struct PythonClass { 1895 | private var typeObject: PythonObject 1896 | 1897 | public struct Members: ExpressibleByDictionaryLiteral { 1898 | public typealias Key = String 1899 | public typealias Value = PythonConvertible 1900 | 1901 | var dictionary: [String: PythonObject] 1902 | 1903 | public init(dictionaryLiteral elements: (Key, Value)...) { 1904 | let castedElements = elements.map { (key, value) in 1905 | (key, value.pythonObject) 1906 | } 1907 | 1908 | dictionary = Dictionary(castedElements, uniquingKeysWith: { _, _ in 1909 | fatalError("Dictionary literal contains duplicate keys") 1910 | }) 1911 | } 1912 | } 1913 | 1914 | public init(_ name: String, superclasses: [PythonObject] = [], members: Members = [:]) { 1915 | self.init(name, superclasses: superclasses, members: members.dictionary) 1916 | } 1917 | 1918 | @_disfavoredOverload 1919 | public init(_ name: String, superclasses: [PythonObject] = [], members: [String: PythonObject] = [:]) { 1920 | var trueSuperclasses = superclasses 1921 | if !trueSuperclasses.contains(Python.object) { 1922 | trueSuperclasses.append(Python.object) 1923 | } 1924 | 1925 | let superclassesTuple = PythonObject(tupleContentsOf: trueSuperclasses) 1926 | typeObject = Python.type(name, superclassesTuple, members.pythonObject) 1927 | } 1928 | } 1929 | 1930 | extension PythonClass : PythonConvertible { 1931 | public var pythonObject: PythonObject { 1932 | typeObject 1933 | } 1934 | } 1935 | -------------------------------------------------------------------------------- /PythonKit/PythonLibrary+Symbols.swift: -------------------------------------------------------------------------------- 1 | //===-- PythonLibrary+Symbols.swift ---------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift.org open source project 4 | // 5 | // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 | // 11 | //===----------------------------------------------------------------------===// 12 | // 13 | // This file defines the Python symbols required for the interoperability layer. 14 | // 15 | //===----------------------------------------------------------------------===// 16 | 17 | //===----------------------------------------------------------------------===// 18 | // Required Python typealiases and constants. 19 | //===----------------------------------------------------------------------===// 20 | 21 | @usableFromInline 22 | typealias PyObjectPointer = UnsafeMutableRawPointer 23 | typealias PyMethodDefPointer = UnsafeMutableRawPointer 24 | typealias PyCCharPointer = UnsafePointer 25 | typealias PyBinaryOperation = 26 | @convention(c) (PyObjectPointer?, PyObjectPointer?) -> PyObjectPointer? 27 | typealias PyUnaryOperation = 28 | @convention(c) (PyObjectPointer?) -> PyObjectPointer? 29 | 30 | let Py_LT: Int32 = 0 31 | let Py_LE: Int32 = 1 32 | let Py_EQ: Int32 = 2 33 | let Py_NE: Int32 = 3 34 | let Py_GT: Int32 = 4 35 | let Py_GE: Int32 = 5 36 | 37 | //===----------------------------------------------------------------------===// 38 | // Python library symbols lazily loaded at runtime. 39 | //===----------------------------------------------------------------------===// 40 | 41 | let Py_Initialize: @convention(c) () -> Void = 42 | PythonLibrary.loadSymbol(name: "Py_Initialize") 43 | 44 | let Py_IncRef: @convention(c) (PyObjectPointer?) -> Void = 45 | PythonLibrary.loadSymbol(name: "Py_IncRef") 46 | 47 | let Py_DecRef: @convention(c) (PyObjectPointer?) -> Void = 48 | PythonLibrary.loadSymbol(name: "Py_DecRef") 49 | 50 | let PyImport_ImportModule: @convention(c) ( 51 | PyCCharPointer) -> PyObjectPointer? = 52 | PythonLibrary.loadSymbol(name: "PyImport_ImportModule") 53 | 54 | let PyEval_GetBuiltins: @convention(c) () -> PyObjectPointer = 55 | PythonLibrary.loadSymbol(name: "PyEval_GetBuiltins") 56 | 57 | let PyRun_SimpleString: @convention(c) (PyCCharPointer) -> Void = 58 | PythonLibrary.loadSymbol(name: "PyRun_SimpleString") 59 | 60 | let PyCFunction_NewEx: @convention(c) (PyMethodDefPointer, UnsafeMutableRawPointer, UnsafeMutableRawPointer?) -> PyObjectPointer = 61 | PythonLibrary.loadSymbol(name: "PyCFunction_NewEx") 62 | 63 | let PyInstanceMethod_New: @convention(c) (PyObjectPointer) -> PyObjectPointer = 64 | PythonLibrary.loadSymbol(name: "PyInstanceMethod_New") 65 | 66 | /// The last argument would ideally be of type `@convention(c) (PyObjectPointer?) -> Void`. 67 | /// Due to SR-15871 and the source-breaking nature of potential workarounds, the 68 | /// static typing was removed. The caller must now manually cast a closure to 69 | /// `OpaquePointer` before passing it into `PyCapsule_New`. 70 | let PyCapsule_New: @convention(c) ( 71 | UnsafeMutableRawPointer, UnsafePointer?, 72 | OpaquePointer) -> PyObjectPointer = 73 | PythonLibrary.loadSymbol(name: "PyCapsule_New") 74 | 75 | let PyCapsule_GetPointer: @convention(c) (PyObjectPointer?, UnsafePointer?) -> UnsafeMutableRawPointer = 76 | PythonLibrary.loadSymbol(name: "PyCapsule_GetPointer") 77 | 78 | let PyErr_SetString: @convention(c) (PyObjectPointer, UnsafePointer?) -> Void = 79 | PythonLibrary.loadSymbol(name: "PyErr_SetString") 80 | 81 | let PyErr_Occurred: @convention(c) () -> PyObjectPointer? = 82 | PythonLibrary.loadSymbol(name: "PyErr_Occurred") 83 | 84 | let PyErr_Clear: @convention(c) () -> Void = 85 | PythonLibrary.loadSymbol(name: "PyErr_Clear") 86 | 87 | let PyErr_Fetch: @convention(c) ( 88 | UnsafeMutablePointer, 89 | UnsafeMutablePointer, 90 | UnsafeMutablePointer) -> Void = 91 | PythonLibrary.loadSymbol(name: "PyErr_Fetch") 92 | 93 | let PyDict_New: @convention(c) () -> PyObjectPointer? = 94 | PythonLibrary.loadSymbol(name: "PyDict_New") 95 | 96 | let PyDict_Contains: @convention(c) ( 97 | PyObjectPointer?, PyObjectPointer?) -> Int32 = 98 | PythonLibrary.loadSymbol(name: "PyDict_Contains") 99 | 100 | let PyDict_SetItem: @convention(c) ( 101 | PyObjectPointer?, PyObjectPointer, PyObjectPointer) -> Void = 102 | PythonLibrary.loadSymbol(name: "PyDict_SetItem") 103 | 104 | let PyObject_GetItem: @convention(c) ( 105 | PyObjectPointer, PyObjectPointer) -> PyObjectPointer? = 106 | PythonLibrary.loadSymbol(name: "PyObject_GetItem") 107 | 108 | let PyObject_SetItem: @convention(c) ( 109 | PyObjectPointer, PyObjectPointer, PyObjectPointer) -> Void = 110 | PythonLibrary.loadSymbol(name: "PyObject_SetItem") 111 | 112 | let PyObject_DelItem: @convention(c) ( 113 | PyObjectPointer, PyObjectPointer) -> Void = 114 | PythonLibrary.loadSymbol(name: "PyObject_DelItem") 115 | 116 | let PyObject_Call: @convention(c) ( 117 | PyObjectPointer, PyObjectPointer, 118 | PyObjectPointer?) -> PyObjectPointer? = 119 | PythonLibrary.loadSymbol(name: "PyObject_Call") 120 | 121 | let PyObject_CallObject: @convention(c) ( 122 | PyObjectPointer, PyObjectPointer) -> PyObjectPointer? = 123 | PythonLibrary.loadSymbol(name: "PyObject_CallObject") 124 | 125 | let PyObject_GetAttrString: @convention(c) ( 126 | PyObjectPointer, PyCCharPointer) -> PyObjectPointer? = 127 | PythonLibrary.loadSymbol(name: "PyObject_GetAttrString") 128 | 129 | let PyObject_SetAttrString: @convention(c) ( 130 | PyObjectPointer, PyCCharPointer, PyObjectPointer) -> Int32 = 131 | PythonLibrary.loadSymbol(name: "PyObject_SetAttrString") 132 | 133 | let PySlice_New: @convention(c) ( 134 | PyObjectPointer?, PyObjectPointer?, 135 | PyObjectPointer?) -> PyObjectPointer? = 136 | PythonLibrary.loadSymbol(name: "PySlice_New") 137 | 138 | let PyTuple_New: @convention(c) (Int) -> PyObjectPointer? = 139 | PythonLibrary.loadSymbol(name: "PyTuple_New") 140 | 141 | let PyTuple_SetItem: @convention(c) ( 142 | PyObjectPointer, Int, PyObjectPointer) -> Void = 143 | PythonLibrary.loadSymbol(name: "PyTuple_SetItem") 144 | 145 | let PyObject_RichCompare: @convention(c) ( 146 | PyObjectPointer, PyObjectPointer, Int32) -> PyObjectPointer? = 147 | PythonLibrary.loadSymbol(name: "PyObject_RichCompare") 148 | 149 | let PyObject_RichCompareBool: @convention(c) ( 150 | PyObjectPointer, PyObjectPointer, Int32) -> Int32 = 151 | PythonLibrary.loadSymbol(name: "PyObject_RichCompareBool") 152 | 153 | let PyDict_Next: @convention(c) ( 154 | PyObjectPointer, UnsafeMutablePointer, 155 | UnsafeMutablePointer, 156 | UnsafeMutablePointer) -> Int32 = 157 | PythonLibrary.loadSymbol(name: "PyDict_Next") 158 | 159 | let PyIter_Next: @convention(c) ( 160 | PyObjectPointer) -> PyObjectPointer? = 161 | PythonLibrary.loadSymbol(name: "PyIter_Next") 162 | 163 | let PyObject_GetIter: @convention(c) ( 164 | PyObjectPointer) -> PyObjectPointer? = 165 | PythonLibrary.loadSymbol(name: "PyObject_GetIter") 166 | 167 | let PyList_New: @convention(c) (Int) -> PyObjectPointer? = 168 | PythonLibrary.loadSymbol(name: "PyList_New") 169 | 170 | let PyList_SetItem: @convention(c) ( 171 | PyObjectPointer, Int, PyObjectPointer) -> Int32 = 172 | PythonLibrary.loadSymbol(name: "PyList_SetItem") 173 | 174 | let PyBool_FromLong: @convention(c) (Int) -> PyObjectPointer = 175 | PythonLibrary.loadSymbol(name: "PyBool_FromLong") 176 | 177 | let PyFloat_AsDouble: @convention(c) (PyObjectPointer) -> Double = 178 | PythonLibrary.loadSymbol(name: "PyFloat_AsDouble") 179 | 180 | let PyFloat_FromDouble: @convention(c) (Double) -> PyObjectPointer = 181 | PythonLibrary.loadSymbol(name: "PyFloat_FromDouble") 182 | 183 | let PyInt_AsLong: @convention(c) (PyObjectPointer) -> Int = 184 | PythonLibrary.loadSymbol( 185 | name: "PyLong_AsLong", 186 | legacyName: "PyInt_AsLong") 187 | 188 | let PyInt_FromLong: @convention(c) (Int) -> PyObjectPointer = 189 | PythonLibrary.loadSymbol( 190 | name: "PyLong_FromLong", 191 | legacyName: "PyInt_FromLong") 192 | 193 | let PyInt_AsUnsignedLongMask: @convention(c) (PyObjectPointer) -> UInt = 194 | PythonLibrary.loadSymbol( 195 | name: "PyLong_AsUnsignedLongMask", 196 | legacyName: "PyInt_AsUnsignedLongMask") 197 | 198 | let PyInt_FromSize_t: @convention(c) (UInt) -> PyObjectPointer = 199 | PythonLibrary.loadSymbol( 200 | name: "PyLong_FromUnsignedLong", 201 | legacyName: "PyInt_FromSize_t") 202 | 203 | let PyString_AsString: @convention(c) (PyObjectPointer) -> PyCCharPointer? = 204 | PythonLibrary.loadSymbol( 205 | name: "PyUnicode_AsUTF8", 206 | legacyName: "PyString_AsString") 207 | 208 | let PyString_FromStringAndSize: @convention(c) ( 209 | PyCCharPointer?, Int) -> (PyObjectPointer?) = 210 | PythonLibrary.loadSymbol( 211 | name: "PyUnicode_DecodeUTF8", 212 | legacyName: "PyString_FromStringAndSize") 213 | 214 | let PyBytes_FromStringAndSize: @convention(c) ( 215 | PyCCharPointer?, Int) -> (PyObjectPointer?) = 216 | PythonLibrary.loadSymbol( 217 | name: "PyBytes_FromStringAndSize", 218 | legacyName: "PyString_FromStringAndSize") 219 | 220 | let PyBytes_AsStringAndSize: @convention(c) ( 221 | PyObjectPointer, 222 | UnsafeMutablePointer?>?, 223 | UnsafeMutablePointer?) -> CInt = 224 | PythonLibrary.loadSymbol( 225 | name: "PyBytes_AsStringAndSize", 226 | legacyName: "PyString_AsStringAndSize") 227 | 228 | let _Py_ZeroStruct: PyObjectPointer = 229 | PythonLibrary.loadSymbol(name: "_Py_ZeroStruct") 230 | 231 | let _Py_TrueStruct: PyObjectPointer = 232 | PythonLibrary.loadSymbol(name: "_Py_TrueStruct") 233 | 234 | let PyBool_Type: PyObjectPointer = 235 | PythonLibrary.loadSymbol(name: "PyBool_Type") 236 | 237 | let PySlice_Type: PyObjectPointer = 238 | PythonLibrary.loadSymbol(name: "PySlice_Type") 239 | 240 | let PyNumber_Add: PyBinaryOperation = 241 | PythonLibrary.loadSymbol(name: "PyNumber_Add") 242 | 243 | let PyNumber_Subtract: PyBinaryOperation = 244 | PythonLibrary.loadSymbol(name: "PyNumber_Subtract") 245 | 246 | let PyNumber_Multiply: PyBinaryOperation = 247 | PythonLibrary.loadSymbol(name: "PyNumber_Multiply") 248 | 249 | let PyNumber_TrueDivide: PyBinaryOperation = 250 | PythonLibrary.loadSymbol(name: "PyNumber_TrueDivide") 251 | 252 | let PyNumber_InPlaceAdd: PyBinaryOperation = 253 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceAdd") 254 | 255 | let PyNumber_InPlaceSubtract: PyBinaryOperation = 256 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceSubtract") 257 | 258 | let PyNumber_InPlaceMultiply: PyBinaryOperation = 259 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceMultiply") 260 | 261 | let PyNumber_InPlaceTrueDivide: PyBinaryOperation = 262 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceTrueDivide") 263 | 264 | let PyNumber_Negative: PyUnaryOperation = 265 | PythonLibrary.loadSymbol(name: "PyNumber_Negative") 266 | 267 | let PyNumber_And: PyBinaryOperation = 268 | PythonLibrary.loadSymbol(name: "PyNumber_And") 269 | 270 | let PyNumber_Or: PyBinaryOperation = 271 | PythonLibrary.loadSymbol(name: "PyNumber_Or") 272 | 273 | let PyNumber_Xor: PyBinaryOperation = 274 | PythonLibrary.loadSymbol(name: "PyNumber_Xor") 275 | 276 | let PyNumber_InPlaceAnd: PyBinaryOperation = 277 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceAnd") 278 | 279 | let PyNumber_InPlaceOr: PyBinaryOperation = 280 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceOr") 281 | 282 | let PyNumber_InPlaceXor: PyBinaryOperation = 283 | PythonLibrary.loadSymbol(name: "PyNumber_InPlaceXor") 284 | 285 | let PyNumber_Invert: PyUnaryOperation = 286 | PythonLibrary.loadSymbol(name: "PyNumber_Invert") 287 | -------------------------------------------------------------------------------- /PythonKit/PythonLibrary.swift: -------------------------------------------------------------------------------- 1 | //===-- PythonLibrary.swift -----------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift.org open source project 4 | // 5 | // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors 10 | // 11 | //===----------------------------------------------------------------------===// 12 | // 13 | // This file implements the logic for dynamically loading Python at runtime. 14 | // 15 | //===----------------------------------------------------------------------===// 16 | 17 | #if canImport(Darwin) 18 | import Darwin 19 | #elseif canImport(Glibc) 20 | import Glibc 21 | #elseif canImport(Musl) 22 | import Musl 23 | #elseif os(Windows) 24 | import CRT 25 | import WinSDK 26 | #endif 27 | 28 | //===----------------------------------------------------------------------===// 29 | // The `PythonLibrary` struct that loads Python symbols at runtime. 30 | //===----------------------------------------------------------------------===// 31 | 32 | public struct PythonLibrary { 33 | public enum Error: Swift.Error, Equatable, CustomStringConvertible { 34 | case pythonLibraryNotFound 35 | 36 | public var description: String { 37 | switch self { 38 | case .pythonLibraryNotFound: 39 | return """ 40 | Python library not found. Set the \(Environment.library.key) \ 41 | environment variable with the path to a Python library. 42 | """ 43 | } 44 | } 45 | } 46 | 47 | private static let pythonInitializeSymbolName = "Py_Initialize" 48 | private static let pythonLegacySymbolName = "PyString_AsString" 49 | 50 | #if canImport(Darwin) 51 | private static let defaultLibraryHandle = UnsafeMutableRawPointer(bitPattern: -2) // RTLD_DEFAULT 52 | #else 53 | private static let defaultLibraryHandle: UnsafeMutableRawPointer? = nil // RTLD_DEFAULT 54 | #endif 55 | 56 | private static var isPythonLibraryLoaded = false 57 | private static var _pythonLibraryHandle: UnsafeMutableRawPointer? 58 | private static var pythonLibraryHandle: UnsafeMutableRawPointer? { 59 | try! PythonLibrary.loadLibrary() 60 | return self._pythonLibraryHandle 61 | } 62 | 63 | /// Tries to load the Python library, will throw an error if no compatible library is found. 64 | public static func loadLibrary() throws { 65 | guard !self.isPythonLibraryLoaded else { return } 66 | let pythonLibraryHandle = self.loadPythonLibrary() 67 | guard self.isPythonLibraryLoaded(at: pythonLibraryHandle) else { 68 | throw Error.pythonLibraryNotFound 69 | } 70 | self.isPythonLibraryLoaded = true 71 | self._pythonLibraryHandle = pythonLibraryHandle 72 | } 73 | 74 | private static let isLegacyPython: Bool = { 75 | let isLegacyPython = PythonLibrary.loadSymbol(PythonLibrary.pythonLibraryHandle, PythonLibrary.pythonLegacySymbolName) != nil 76 | if isLegacyPython { 77 | PythonLibrary.log("Loaded legacy Python library, using legacy symbols...") 78 | } 79 | return isLegacyPython 80 | }() 81 | 82 | internal static func loadSymbol( 83 | name: String, legacyName: String? = nil, type: T.Type = T.self) -> T { 84 | var name = name 85 | if let legacyName = legacyName, self.isLegacyPython { 86 | name = legacyName 87 | } 88 | 89 | log("Loading symbol '\(name)' from the Python library...") 90 | return unsafeBitCast(self.loadSymbol(self.pythonLibraryHandle, name), to: type) 91 | } 92 | } 93 | 94 | // Methods of `PythonLibrary` required to load the Python library. 95 | extension PythonLibrary { 96 | private static let supportedMajorVersions: [Int] = [3, 2] 97 | private static let supportedMinorVersions: [Int] = Array(0...30).reversed() 98 | 99 | private static let libraryPathVersionCharacter: Character = ":" 100 | 101 | #if canImport(Darwin) 102 | private static var libraryNames = ["Python.framework/Versions/:/Python"] 103 | private static var libraryPathExtensions = [""] 104 | private static var librarySearchPaths = ["", "/opt/homebrew/Frameworks/", "/usr/local/Frameworks/"] 105 | private static var libraryVersionSeparator = "." 106 | #elseif os(Linux) 107 | private static var libraryNames = ["libpython:", "libpython:m"] 108 | private static var libraryPathExtensions = [".so"] 109 | private static var librarySearchPaths = [""] 110 | private static var libraryVersionSeparator = "." 111 | #elseif os(Windows) 112 | private static var libraryNames = ["python:"] 113 | private static var libraryPathExtensions = [".dll"] 114 | private static var librarySearchPaths = [""] 115 | private static var libraryVersionSeparator = "" 116 | #endif 117 | 118 | private static let libraryPaths: [String] = { 119 | var libraryPaths: [String] = [] 120 | for librarySearchPath in librarySearchPaths { 121 | for libraryName in libraryNames { 122 | for libraryPathExtension in libraryPathExtensions { 123 | let libraryPath = 124 | librarySearchPath + libraryName + libraryPathExtension 125 | libraryPaths.append(libraryPath) 126 | } 127 | } 128 | } 129 | return libraryPaths 130 | }() 131 | 132 | private static func loadSymbol( 133 | _ libraryHandle: UnsafeMutableRawPointer?, _ name: String) -> UnsafeMutableRawPointer? { 134 | #if os(Windows) 135 | guard let libraryHandle = libraryHandle else { return nil } 136 | let moduleHandle = libraryHandle 137 | .assumingMemoryBound(to: HINSTANCE__.self) 138 | let moduleSymbol = GetProcAddress(moduleHandle, name) 139 | return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self) 140 | #else 141 | return dlsym(libraryHandle, name) 142 | #endif 143 | } 144 | 145 | private static func isPythonLibraryLoaded(at pythonLibraryHandle: UnsafeMutableRawPointer? = nil) -> Bool { 146 | let pythonLibraryHandle = pythonLibraryHandle ?? self.defaultLibraryHandle 147 | return self.loadSymbol(pythonLibraryHandle, self.pythonInitializeSymbolName) != nil 148 | } 149 | 150 | private static func loadPythonLibrary() -> UnsafeMutableRawPointer? { 151 | let pythonLibraryHandle: UnsafeMutableRawPointer? 152 | if self.isPythonLibraryLoaded() { 153 | pythonLibraryHandle = self.defaultLibraryHandle 154 | } 155 | else if let pythonLibraryPath = Environment.library.value { 156 | pythonLibraryHandle = self.loadPythonLibrary(at: pythonLibraryPath) 157 | } 158 | else { 159 | pythonLibraryHandle = self.findAndLoadExternalPythonLibrary() 160 | } 161 | return pythonLibraryHandle 162 | } 163 | 164 | private static func findAndLoadExternalPythonLibrary() -> UnsafeMutableRawPointer? { 165 | for majorVersion in supportedMajorVersions { 166 | for minorVersion in supportedMinorVersions { 167 | for libraryPath in libraryPaths { 168 | let version = PythonVersion(major: majorVersion, minor: minorVersion) 169 | guard let pythonLibraryHandle = loadPythonLibrary( 170 | at: libraryPath, version: version) else { 171 | continue 172 | } 173 | return pythonLibraryHandle 174 | } 175 | } 176 | } 177 | return nil 178 | } 179 | 180 | private static func loadPythonLibrary( 181 | at path: String, version: PythonVersion) -> UnsafeMutableRawPointer? { 182 | let versionString = version.versionString 183 | 184 | if let requiredPythonVersion = Environment.version.value { 185 | let requiredMajorVersion = Int(requiredPythonVersion) 186 | if requiredPythonVersion != versionString, 187 | requiredMajorVersion != version.major { 188 | return nil 189 | } 190 | } 191 | 192 | let libraryVersionString = versionString 193 | .split(separator: PythonVersion.versionSeparator) 194 | .joined(separator: libraryVersionSeparator) 195 | let path = path.split(separator: libraryPathVersionCharacter) 196 | .joined(separator: libraryVersionString) 197 | return self.loadPythonLibrary(at: path) 198 | } 199 | 200 | private static func loadPythonLibrary(at path: String) -> UnsafeMutableRawPointer? { 201 | self.log("Trying to load library at '\(path)'...") 202 | #if os(Windows) 203 | let pythonLibraryHandle = UnsafeMutableRawPointer(LoadLibraryA(path)) 204 | #else 205 | // Must be RTLD_GLOBAL because subsequent .so files from the imported python 206 | // modules may depend on this .so file. 207 | let pythonLibraryHandle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL) 208 | if pythonLibraryHandle == nil { 209 | self.log("Failed to load library at '\(path)'.") 210 | if let errorCString = dlerror() { 211 | let errorString = String(cString: errorCString) 212 | self.log("Reason for failure: \(errorString)") 213 | } 214 | } 215 | #endif 216 | 217 | if pythonLibraryHandle != nil { 218 | self.log("Library at '\(path)' was successfully loaded.") 219 | } 220 | return pythonLibraryHandle 221 | } 222 | } 223 | 224 | // Methods of `PythonLibrary` required to set a given Python version or library path. 225 | extension PythonLibrary { 226 | private static func enforceNonLoadedPythonLibrary(function: String = #function) { 227 | precondition(!self.isPythonLibraryLoaded, """ 228 | Error: \(function) should not be called after any Python library \ 229 | has already been loaded. 230 | """) 231 | } 232 | 233 | /// Use the Python library with the specified version. 234 | /// - Parameters: 235 | /// - major: Major version or nil to use any Python version. 236 | /// - minor: Minor version or nil to use any minor version. 237 | public static func useVersion(_ major: Int?, _ minor: Int? = nil) { 238 | self.enforceNonLoadedPythonLibrary() 239 | let version = PythonVersion(major: major, minor: minor) 240 | PythonLibrary.Environment.version.set(version.versionString) 241 | } 242 | 243 | /// Use the Python library at the specified path. 244 | /// - Parameter path: Path of the Python library to load or nil to use the default search path. 245 | public static func useLibrary(at path: String?) { 246 | self.enforceNonLoadedPythonLibrary() 247 | PythonLibrary.Environment.library.set(path ?? "") 248 | } 249 | } 250 | 251 | // `PythonVersion` struct that defines a given Python version. 252 | extension PythonLibrary { 253 | private struct PythonVersion { 254 | let major: Int? 255 | let minor: Int? 256 | 257 | static let versionSeparator: Character = "." 258 | 259 | init(major: Int?, minor: Int?) { 260 | precondition(!(major == nil && minor != nil), """ 261 | Error: The Python library minor version cannot be specified \ 262 | without the major version. 263 | """) 264 | self.major = major 265 | self.minor = minor 266 | } 267 | 268 | var versionString: String { 269 | guard let major = major else { return "" } 270 | var versionString = String(major) 271 | if let minor = minor { 272 | versionString += "\(PythonVersion.versionSeparator)\(minor)" 273 | } 274 | return versionString 275 | } 276 | } 277 | } 278 | 279 | // `PythonLibrary.Environment` enum used to read and set environment variables. 280 | extension PythonLibrary { 281 | private enum Environment: String { 282 | private static let keyPrefix = "PYTHON" 283 | private static let keySeparator = "_" 284 | 285 | case library = "LIBRARY" 286 | case version = "VERSION" 287 | case loaderLogging = "LOADER_LOGGING" 288 | 289 | var key: String { 290 | return Environment.keyPrefix + Environment.keySeparator + rawValue 291 | } 292 | 293 | var value: String? { 294 | guard let cString = getenv(key) else { return nil } 295 | let value = String(cString: cString) 296 | guard !value.isEmpty else { return nil } 297 | return value 298 | } 299 | 300 | func set(_ value: String) { 301 | #if os(Windows) 302 | _putenv_s(key, value) 303 | #else 304 | setenv(key, value, 1) 305 | #endif 306 | } 307 | } 308 | } 309 | 310 | // Methods of `PythonLibrary` used for logging messages. 311 | extension PythonLibrary { 312 | private static func log(_ message: String) { 313 | guard Environment.loaderLogging.value != nil else { return } 314 | fputs(message + "\n", stderr) 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # PythonKit 3 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pvieito/PythonKit) 4 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pvieito/PythonKit) 5 | 6 | Swift framework to interact with Python. 7 | 8 | ## Usage 9 | 10 | Some Python code like this: 11 | 12 | ```python 13 | import sys 14 | 15 | print(f"Python {sys.version_info.major}.{sys.version_info.minor}") 16 | print(f"Python Version: {sys.version}") 17 | print(f"Python Encoding: {sys.getdefaultencoding().upper()}") 18 | ``` 19 | 20 | Can be implemented in Swift through PythonKit with the following code: 21 | 22 | ```swift 23 | import PythonKit 24 | 25 | let sys = Python.import("sys") 26 | 27 | print("Python \(sys.version_info.major).\(sys.version_info.minor)") 28 | print("Python Version: \(sys.version)") 29 | print("Python Encoding: \(sys.getdefaultencoding().upper())") 30 | ``` 31 | 32 | ### Swift Package Manager 33 | 34 | Add the following dependency to your `Package.swift` manifest: 35 | 36 | ```swift 37 | .package(url: "https://github.com/pvieito/PythonKit.git", branch: "master"), 38 | ``` 39 | 40 | ## Environment Variables 41 | 42 | As the Python library are loaded at runtime by **PythonKit**, it will try to find the most modern Python version available in the system. You can force a given version with the `PYTHON_VERSION` environment variable or an specific Python library path or name with `PYTHON_LIBRARY`. 43 | 44 | ``` 45 | $ PYTHON_VERSION=3 swift run 46 | [*] Python 3.5 47 | $ PYTHON_VERSION=2.7 swift run 48 | [*] Python 2.7 49 | $ PYTHON_LIBRARY=libpython3.5.so swift run 50 | [*] Python 3.5 51 | $ PYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython2.7.so swift run 52 | [*] Python 2.7 53 | ``` 54 | 55 | If **PythonKit** cannot find and load the Python library you can set the `PYTHON_LOADER_LOGGING` environment variable to know from which locations **PythonKit** is trying to load the library: 56 | 57 | ``` 58 | $ PYTHON_LOADER_LOGGING=TRUE PYTHON_VERSION=3.8 swift run 59 | Loading symbol 'Py_Initialize' from the Python library... 60 | Trying to load library at 'Python.framework/Versions/3.8/Python'... 61 | Trying to load library at '/usr/local/Frameworks/Python.framework/Versions/3.8/Python'... 62 | Fatal error: Python library not found. Set the PYTHON_LIBRARY environment variable with the path to a Python library. 63 | ``` 64 | 65 | ## Troubleshooting 66 | 67 | - If your are targeting the Mac platform with the [Hardened Runtime](https://developer.apple.com/documentation/security/hardened_runtime) enabled make sure you are properly signing and embedding the Python framework you are trying to load with **PythonKit**. The Hardened Runtime [Library Validation mechanism](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation) prevents a process from loading libraries that are not signed by Apple or the same developer as the main process. 68 | 69 | 70 | ## Notes 71 | 72 | - Originally **PythonKit** was based on the `Python` module from the [**Swift for TensorFlow**](https://github.com/tensorflow/swift) experimental project. 73 | - If you have questions about **PythonKit** you can ask on the [**Swift Forums**](https://forums.swift.org/c/related-projects/). 74 | -------------------------------------------------------------------------------- /Tests/PythonKitTests/NumpyConversionTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import PythonKit 3 | 4 | class NumpyConversionTests: XCTestCase { 5 | static var numpyModule = try? Python.attemptImport("numpy") 6 | 7 | func testArrayConversion() { 8 | guard let np = NumpyConversionTests.numpyModule else { return } 9 | 10 | let numpyArrayEmpty = np.array([] as [Float], dtype: np.float32) 11 | XCTAssertEqual([], Array(numpy: numpyArrayEmpty)) 12 | 13 | let numpyArrayBool = np.array([true, false, false, true]) 14 | XCTAssertEqual([true, false, false, true], Array(numpy: numpyArrayBool)) 15 | 16 | let numpyArrayFloat = np.ones([6], dtype: np.float32) 17 | XCTAssertEqual(Array(repeating: 1, count: 6), Array(numpy: numpyArrayFloat)) 18 | 19 | let numpyArrayInt32 = np.array([-1, 4, 25, 2018], dtype: np.int32) 20 | XCTAssertEqual([-1, 4, 25, 2018], Array(numpy: numpyArrayInt32)) 21 | 22 | let numpyArray2D = np.ones([2, 3]) 23 | XCTAssertNil(Array(numpy: numpyArray2D)) 24 | 25 | let numpyArrayStrided = np.array([[1, 2], [1, 2]], dtype: np.int32)[ 26 | Python.slice(Python.None), 1] 27 | // Assert that the array has a stride, so that we're certainly testing a 28 | // strided array. 29 | XCTAssertNotEqual(numpyArrayStrided.__array_interface__["strides"], Python.None) 30 | XCTAssertEqual([2, 2], Array(numpy: numpyArrayStrided)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/PythonKitTests/PythonFunctionTests.swift: -------------------------------------------------------------------------------- 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 { args in 17 | let lhs = args[0] 18 | let rhs = args[1] 19 | return lhs + rhs 20 | }.pythonObject 21 | 22 | let pythonSum = pythonAdd(2, 3) 23 | XCTAssertNotNil(Double(pythonSum)) 24 | XCTAssertEqual(pythonSum, 5) 25 | 26 | // Test function with keyword arguments 27 | 28 | // Since there is no alternative function signature, `args` and `kwargs` 29 | // can be used without manually stating their type. This differs from 30 | // the behavior when there are no keywords. 31 | let pythonSelect = PythonFunction { args, kwargs in 32 | // NOTE: This may fail on Python versions before 3.6 because they do 33 | // not preserve order of keyword arguments 34 | XCTAssertEqual(args[0], true) 35 | XCTAssertEqual(kwargs[0].key, "y") 36 | XCTAssertEqual(kwargs[0].value, 2) 37 | XCTAssertEqual(kwargs[1].key, "x") 38 | XCTAssertEqual(kwargs[1].value, 3) 39 | 40 | let conditional = Bool(args[0])! 41 | let xIndex = kwargs.firstIndex(where: { $0.key == "x" })! 42 | let yIndex = kwargs.firstIndex(where: { $0.key == "y" })! 43 | 44 | return kwargs[conditional ? xIndex : yIndex].value 45 | }.pythonObject 46 | 47 | let pythonSelectOutput = pythonSelect(true, y: 2, x: 3) 48 | XCTAssertEqual(pythonSelectOutput, 3) 49 | } 50 | 51 | // From https://www.geeksforgeeks.org/create-classes-dynamically-in-python 52 | func testPythonClassConstruction() { 53 | guard canUsePythonFunction else { 54 | return 55 | } 56 | 57 | let constructor = PythonInstanceMethod { args in 58 | let `self` = args[0] 59 | `self`.constructor_arg = args[1] 60 | return Python.None 61 | } 62 | 63 | // Instead of calling `print`, use this to test what would be output. 64 | var printOutput: String? 65 | 66 | // Example of function using an alternative syntax for `args`. 67 | let displayMethod = PythonInstanceMethod { (args: [PythonObject]) in 68 | // let `self` = args[0] 69 | printOutput = String(args[1]) 70 | return Python.None 71 | } 72 | 73 | let classMethodOriginal = PythonInstanceMethod { args in 74 | // let cls = args[0] 75 | printOutput = String(args[1]) 76 | return Python.None 77 | } 78 | 79 | // Did not explicitly convert `constructor` or `displayMethod` to 80 | // PythonObject. This is intentional, as the `PythonClass` initializer 81 | // should take any `PythonConvertible` and not just `PythonObject`. 82 | let classMethod = Python.classmethod(classMethodOriginal.pythonObject) 83 | 84 | let Geeks = PythonClass("Geeks", members: [ 85 | // Constructor 86 | "__init__": constructor, 87 | 88 | // Data members 89 | "string_attribute": "Geeks 4 geeks!", 90 | "int_attribute": 1706256, 91 | 92 | // Member functions 93 | "func_arg": displayMethod, 94 | "class_func": classMethod, 95 | ]).pythonObject 96 | 97 | let obj = Geeks("constructor argument") 98 | XCTAssertEqual(obj.constructor_arg, "constructor argument") 99 | XCTAssertEqual(obj.string_attribute, "Geeks 4 geeks!") 100 | XCTAssertEqual(obj.int_attribute, 1706256) 101 | 102 | obj.func_arg("Geeks for Geeks") 103 | XCTAssertEqual(printOutput, "Geeks for Geeks") 104 | 105 | Geeks.class_func("Class Dynamically Created!") 106 | XCTAssertEqual(printOutput, "Class Dynamically Created!") 107 | } 108 | 109 | // Previously, there was a build error where passing a simple 110 | // `PythonClass.Members` literal made the literal's type ambiguous. It was 111 | // confused with `[String: PythonObject]`. The solution was adding a 112 | // `@_disfavoredOverload` attribute to the more specific initializer. 113 | func testPythonClassInitializer() { 114 | guard canUsePythonFunction else { 115 | return 116 | } 117 | 118 | let MyClass = PythonClass( 119 | "MyClass", 120 | superclasses: [Python.object], 121 | members: [ 122 | "memberName": "memberValue", 123 | ] 124 | ).pythonObject 125 | 126 | let memberValue = MyClass().memberName 127 | XCTAssertEqual(String(memberValue), "memberValue") 128 | } 129 | 130 | func testPythonClassInheritance() { 131 | guard canUsePythonFunction else { 132 | return 133 | } 134 | 135 | var helloOutput: String? 136 | var helloWorldOutput: String? 137 | 138 | // Declare subclasses of `Python.Exception` 139 | 140 | let HelloException = PythonClass( 141 | "HelloException", 142 | superclasses: [Python.Exception], 143 | members: [ 144 | "str_prefix": "HelloException-prefix ", 145 | 146 | "__init__": PythonInstanceMethod { args in 147 | let `self` = args[0] 148 | let message = "hello \(args[1])" 149 | helloOutput = String(message) 150 | 151 | // Conventional `super` syntax does not work; use this instead. 152 | Python.Exception.__init__(`self`, message) 153 | return Python.None 154 | }, 155 | 156 | // Example of function using the `self` convention instead of `args`. 157 | "__str__": PythonInstanceMethod { (`self`: PythonObject) in 158 | return `self`.str_prefix + Python.repr(`self`) 159 | } 160 | ] 161 | ).pythonObject 162 | 163 | let HelloWorldException = PythonClass( 164 | "HelloWorldException", 165 | superclasses: [HelloException], 166 | members: [ 167 | "str_prefix": "HelloWorldException-prefix ", 168 | 169 | "__init__": PythonInstanceMethod { args in 170 | let `self` = args[0] 171 | let message = "world \(args[1])" 172 | helloWorldOutput = String(message) 173 | 174 | `self`.int_param = args[2] 175 | 176 | // Conventional `super` syntax does not work; use this instead. 177 | HelloException.__init__(`self`, message) 178 | return Python.None 179 | }, 180 | 181 | // Example of function using the `self` convention instead of `args`. 182 | "custom_method": PythonInstanceMethod { (`self`: PythonObject) in 183 | return `self`.int_param 184 | } 185 | ] 186 | ).pythonObject 187 | 188 | // Test that inheritance works as expected 189 | 190 | let error1 = HelloException("test 1") 191 | XCTAssertEqual(helloOutput, "hello test 1") 192 | XCTAssertEqual(Python.str(error1), "HelloException-prefix HelloException('hello test 1')") 193 | XCTAssertEqual(Python.repr(error1), "HelloException('hello test 1')") 194 | 195 | let error2 = HelloWorldException("test 1", 123) 196 | XCTAssertEqual(helloOutput, "hello world test 1") 197 | XCTAssertEqual(helloWorldOutput, "world test 1") 198 | XCTAssertEqual(Python.str(error2), "HelloWorldException-prefix HelloWorldException('hello world test 1')") 199 | XCTAssertEqual(Python.repr(error2), "HelloWorldException('hello world test 1')") 200 | XCTAssertEqual(error2.custom_method(), 123) 201 | XCTAssertNotEqual(error2.custom_method(), "123") 202 | 203 | // Test that subclasses behave like Python exceptions 204 | 205 | // Example of function with no named parameters, which can be stated 206 | // ergonomically using an underscore. The ignored input is a [PythonObject]. 207 | let _ = PythonFunction { _ in 208 | throw HelloWorldException("EXAMPLE ERROR MESSAGE", 2) 209 | }.pythonObject 210 | 211 | /* 212 | do { 213 | try testFunction.throwing.dynamicallyCall(withArguments: []) 214 | XCTFail("testFunction did not throw an error.") 215 | } catch PythonError.exception(let error, _) { 216 | guard let description = String(error) else { 217 | XCTFail("A string could not be created from a HelloWorldException.") 218 | return 219 | } 220 | 221 | XCTAssertTrue(description.contains("EXAMPLE ERROR MESSAGE")) 222 | XCTAssertTrue(description.contains("HelloWorldException")) 223 | } catch { 224 | XCTFail("Got error that was not a Python exception: \(error.localizedDescription)") 225 | } 226 | */ 227 | } 228 | 229 | // Tests the ability to dynamically construct an argument list with keywords 230 | // and instantiate a `PythonInstanceMethod` with keywords. 231 | func testPythonClassInheritanceWithKeywords() { 232 | guard canUsePythonFunction else { 233 | return 234 | } 235 | 236 | func getValue(key: String, kwargs: [(String, PythonObject)]) -> PythonObject { 237 | let index = kwargs.firstIndex(where: { $0.0 == key })! 238 | return kwargs[index].1 239 | } 240 | 241 | // Base class has the following arguments: 242 | // __init__(): 243 | // - 1 unnamed argument 244 | // - param1 245 | // - param2 246 | // 247 | // test_method(): 248 | // - param1 249 | // - param2 250 | 251 | let BaseClass = PythonClass( 252 | "BaseClass", 253 | superclasses: [], 254 | members: [ 255 | "__init__": PythonInstanceMethod { args, kwargs in 256 | let `self` = args[0] 257 | `self`.arg1 = args[1] 258 | `self`.param1 = getValue(key: "param1", kwargs: kwargs) 259 | `self`.param2 = getValue(key: "param2", kwargs: kwargs) 260 | return Python.None 261 | }, 262 | 263 | "test_method": PythonInstanceMethod { args, kwargs in 264 | let `self` = args[0] 265 | `self`.param1 += getValue(key: "param1", kwargs: kwargs) 266 | `self`.param2 += getValue(key: "param2", kwargs: kwargs) 267 | return Python.None 268 | } 269 | ] 270 | ).pythonObject 271 | 272 | // Derived class accepts the following arguments: 273 | // __init__(): 274 | // - param2 275 | // - param3 276 | // 277 | // test_method(): 278 | // - param1 279 | // - param2 280 | // - param3 281 | 282 | let DerivedClass = PythonClass( 283 | "DerivedClass", 284 | superclasses: [], 285 | members: [ 286 | "__init__": PythonInstanceMethod { args, kwargs in 287 | let `self` = args[0] 288 | `self`.param3 = getValue(key: "param3", kwargs: kwargs) 289 | 290 | // Lists the arguments in an order different than they are 291 | // specified (self, param2, param3, param1, arg1). The 292 | // correct order is (self, arg1, param1, param2, param3). 293 | let newKeywordArguments = args.map { 294 | ("", $0) 295 | } + kwargs + [ 296 | ("param1", 1), 297 | ("", 0) 298 | ] 299 | 300 | BaseClass.__init__.dynamicallyCall( 301 | withKeywordArguments: newKeywordArguments) 302 | return Python.None 303 | }, 304 | 305 | "test_method": PythonInstanceMethod { args, kwargs in 306 | let `self` = args[0] 307 | `self`.param3 += getValue(key: "param3", kwargs: kwargs) 308 | 309 | BaseClass.test_method.dynamicallyCall( 310 | withKeywordArguments: args.map { ("", $0) } + kwargs) 311 | return Python.None 312 | } 313 | ] 314 | ).pythonObject 315 | 316 | let derivedInstance = DerivedClass(param2: 2, param3: 3) 317 | XCTAssertEqual(derivedInstance.arg1, 0) 318 | XCTAssertEqual(derivedInstance.param1, 1) 319 | XCTAssertEqual(derivedInstance.param2, 2) 320 | XCTAssertEqual(derivedInstance.checking.param3, 3) 321 | 322 | derivedInstance.test_method(param1: 1, param2: 2, param3: 3) 323 | XCTAssertEqual(derivedInstance.arg1, 0) 324 | XCTAssertEqual(derivedInstance.param1, 2) 325 | XCTAssertEqual(derivedInstance.param2, 4) 326 | XCTAssertEqual(derivedInstance.checking.param3, 6) 327 | 328 | // Validate that subclassing and instantiating the derived class does 329 | // not affect behavior of the parent class. 330 | 331 | let baseInstance = BaseClass(0, param1: 10, param2: 20) 332 | XCTAssertEqual(baseInstance.arg1, 0) 333 | XCTAssertEqual(baseInstance.param1, 10) 334 | XCTAssertEqual(baseInstance.param2, 20) 335 | XCTAssertEqual(baseInstance.checking.param3, nil) 336 | 337 | baseInstance.test_method(param1: 10, param2: 20) 338 | XCTAssertEqual(baseInstance.arg1, 0) 339 | XCTAssertEqual(baseInstance.param1, 20) 340 | XCTAssertEqual(baseInstance.param2, 40) 341 | XCTAssertEqual(baseInstance.checking.param3, nil) 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /Tests/PythonKitTests/PythonRuntimeTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import PythonKit 3 | 4 | class PythonRuntimeTests: XCTestCase { 5 | func testCheckVersion() { 6 | XCTAssertGreaterThanOrEqual(Python.versionInfo.major, 2) 7 | XCTAssertGreaterThanOrEqual(Python.versionInfo.minor, 0) 8 | } 9 | 10 | func testPythonList() { 11 | let list: PythonObject = [0, 1, 2] 12 | XCTAssertEqual("[0, 1, 2]", list.description) 13 | XCTAssertEqual(3, Python.len(list)) 14 | XCTAssertEqual("[0, 1, 2]", Python.str(list)) 15 | 16 | let polymorphicList = PythonObject(["a", 2, true, 1.5]) 17 | XCTAssertEqual("a", polymorphicList[0]) 18 | XCTAssertEqual(2, polymorphicList[1]) 19 | XCTAssertEqual(true, polymorphicList[2]) 20 | XCTAssertEqual(1.5, polymorphicList[3]) 21 | XCTAssertEqual(1.5, polymorphicList[-1]) 22 | 23 | XCTAssertEqual(4, polymorphicList.count as Int) 24 | XCTAssertEqual(4, polymorphicList.checking.count!) 25 | XCTAssertEqual(4, polymorphicList.throwing.count!) 26 | 27 | polymorphicList[2] = 2 28 | XCTAssertEqual(2, polymorphicList[2]) 29 | } 30 | 31 | #if !os(Windows) 32 | func testPythonDict() { 33 | let dict: PythonObject = ["a": 1, 1: 0.5] 34 | XCTAssertEqual(2, Python.len(dict)) 35 | XCTAssertEqual(1, dict["a"]) 36 | XCTAssertEqual(0.5, dict[1]) 37 | 38 | XCTAssertEqual(2, dict.count as Int) 39 | XCTAssertEqual(2, dict.checking.count!) 40 | XCTAssertEqual(2, dict.throwing.count!) 41 | 42 | dict["b"] = "c" 43 | XCTAssertEqual("c", dict["b"]) 44 | dict["b"] = "d" 45 | XCTAssertEqual("d", dict["b"]) 46 | 47 | // Dictionary initializer patch does not work on Python 2, but that 48 | // version is no longer being actively supported. 49 | guard Python.versionInfo.major >= 3 else { 50 | return 51 | } 52 | 53 | // Pandas DataFrame regression test spotted in Jupyter. This is 54 | // non-deterministic, so repeat it several times to ensure the bug does 55 | // not appear. 56 | for _ in 0..<15 { 57 | let records: [PythonObject] = [ 58 | ["col 1": 3, "col 2": 5], 59 | ["col 1": 8, "col 2": 2] 60 | ] 61 | let records_description = 62 | "[{'col 1': 3, 'col 2': 5}, {'col 1': 8, 'col 2': 2}]" 63 | XCTAssertEqual(String(describing: records), records_description) 64 | 65 | let records_alt: [PythonObject] = [ 66 | ["col 1": 3, "col 2": 5, "col 3": 4], 67 | ["col 1": 8, "col 2": 2, "col 3": 4] 68 | ] 69 | let records_alt_description = 70 | "[{'col 1': 3, 'col 2': 5, 'col 3': 4}, {'col 1': 8, 'col 2': 2, 'col 3': 4}]" 71 | XCTAssertEqual(String(describing: records_alt), records_alt_description) 72 | } 73 | } 74 | 75 | func testRange() { 76 | let slice = PythonObject(5..<10) 77 | XCTAssertEqual(Python.slice(5, 10), slice) 78 | XCTAssertEqual(5, slice.start) 79 | XCTAssertEqual(10, slice.stop) 80 | 81 | let range = Range(slice) 82 | XCTAssertNotNil(range) 83 | XCTAssertEqual(5, range?.lowerBound) 84 | XCTAssertEqual(10, range?.upperBound) 85 | 86 | XCTAssertNil(Range(PythonObject(5...))) 87 | } 88 | 89 | func testPartialRangeFrom() { 90 | let slice = PythonObject(5...) 91 | XCTAssertEqual(Python.slice(5, Python.None), slice) 92 | XCTAssertEqual(5, slice.start) 93 | 94 | let range = PartialRangeFrom(slice) 95 | XCTAssertNotNil(range) 96 | XCTAssertEqual(5, range?.lowerBound) 97 | 98 | XCTAssertNil(PartialRangeFrom(PythonObject(..<5))) 99 | } 100 | 101 | func testPartialRangeUpTo() { 102 | let slice = PythonObject(..<5) 103 | XCTAssertEqual(Python.slice(5), slice) 104 | XCTAssertEqual(5, slice.stop) 105 | 106 | let range = PartialRangeUpTo(slice) 107 | XCTAssertNotNil(range) 108 | XCTAssertEqual(5, range?.upperBound) 109 | 110 | XCTAssertNil(PartialRangeUpTo(PythonObject(5...))) 111 | } 112 | #endif 113 | 114 | func testStrideable() { 115 | let strideTo = stride(from: PythonObject(0), to: 100, by: 2) 116 | XCTAssertEqual(0, strideTo.min()!) 117 | XCTAssertEqual(98, strideTo.max()!) 118 | XCTAssertEqual([0, 2, 4, 6, 8], Array(strideTo.prefix(5))) 119 | XCTAssertEqual([90, 92, 94, 96, 98], Array(strideTo.suffix(5))) 120 | 121 | let strideThrough = stride(from: PythonObject(0), through: 100, by: 2) 122 | XCTAssertEqual(0, strideThrough.min()!) 123 | XCTAssertEqual(100, strideThrough.max()!) 124 | XCTAssertEqual([0, 2, 4, 6, 8], Array(strideThrough.prefix(5))) 125 | XCTAssertEqual([92, 94, 96, 98, 100], Array(strideThrough.suffix(5))) 126 | } 127 | 128 | func testBinaryOps() { 129 | XCTAssertEqual(42, PythonObject(42)) 130 | XCTAssertEqual(42, PythonObject(2) + PythonObject(40)) 131 | XCTAssertEqual(2, PythonObject(2) * PythonObject(3) + PythonObject(-4)) 132 | 133 | XCTAssertEqual("abcdef", PythonObject("ab") + 134 | PythonObject("cde") + 135 | PythonObject("") + 136 | PythonObject("f")) 137 | XCTAssertEqual("ababab", PythonObject("ab") * 3) 138 | 139 | var x = PythonObject(2) 140 | x += 3 141 | XCTAssertEqual(5, x) 142 | x *= 2 143 | XCTAssertEqual(10, x) 144 | x -= 3 145 | XCTAssertEqual(7, x) 146 | x /= 2 147 | XCTAssertEqual(3.5, x) 148 | x += -1 149 | XCTAssertEqual(2.5, x) 150 | } 151 | 152 | func testUnaryOps() { 153 | var x = PythonObject(5) 154 | x = -x 155 | XCTAssertEqual(-5, x) 156 | x = PythonObject(-5) 157 | x = -x 158 | XCTAssertEqual(5, x) 159 | } 160 | 161 | func testComparable() { 162 | let array: [PythonObject] = [-1, 10, 1, 0, 0] 163 | XCTAssertEqual([-1, 0, 0, 1, 10], array.sorted()) 164 | let list: PythonObject = [-1, 10, 1, 0, 0] 165 | XCTAssertEqual([-1, 0, 0, 1, 10], list.sorted()) 166 | } 167 | 168 | #if !os(Windows) 169 | func testHashable() { 170 | func compareHashValues(_ x: PythonConvertible) { 171 | let a = x.pythonObject 172 | let b = x.pythonObject 173 | XCTAssertEqual(a.hashValue, b.hashValue) 174 | } 175 | 176 | compareHashValues(1) 177 | compareHashValues(3.14) 178 | compareHashValues("asdf") 179 | compareHashValues(PythonObject(tupleOf: 1, 2, 3)) 180 | } 181 | #endif 182 | 183 | func testRangeIteration() { 184 | for (index, val) in Python.range(5).enumerated() { 185 | XCTAssertEqual(PythonObject(index), val) 186 | } 187 | } 188 | 189 | func testErrors() { 190 | XCTAssertThrowsError( 191 | try PythonObject(1).__truediv__.throwing.dynamicallyCall(withArguments: 0) 192 | ) { 193 | guard case let PythonError.exception(exception, _) = $0 else { 194 | XCTFail("non-Python error: \($0)") 195 | return 196 | } 197 | XCTAssertEqual(exception.__class__.__name__, "ZeroDivisionError") 198 | } 199 | } 200 | 201 | #if !os(Windows) 202 | func testTuple() { 203 | let element1: PythonObject = 0 204 | let element2: PythonObject = "abc" 205 | let element3: PythonObject = [0, 0] 206 | let element4: PythonObject = ["a": 0, "b": "c"] 207 | let pair = PythonObject(tupleOf: element1, element2) 208 | let (pair1, pair2) = pair.tuple2 209 | XCTAssertEqual(element1, pair1) 210 | XCTAssertEqual(element2, pair2) 211 | 212 | let triple = PythonObject(tupleOf: element1, element2, element3) 213 | let (triple1, triple2, triple3) = triple.tuple3 214 | XCTAssertEqual(element1, triple1) 215 | XCTAssertEqual(element2, triple2) 216 | XCTAssertEqual(element3, triple3) 217 | 218 | let quadruple = PythonObject(tupleOf: element1, element2, element3, element4) 219 | let (quadruple1, quadruple2, quadruple3, quadruple4) = quadruple.tuple4 220 | XCTAssertEqual(element1, quadruple1) 221 | XCTAssertEqual(element2, quadruple2) 222 | XCTAssertEqual(element3, quadruple3) 223 | XCTAssertEqual(element4, quadruple4) 224 | 225 | XCTAssertEqual(element2, quadruple[1]) 226 | } 227 | #endif 228 | 229 | func testMethodCalling() { 230 | let list: PythonObject = [1, 2] 231 | list.append(3) 232 | XCTAssertEqual([1, 2, 3], list) 233 | 234 | // Check method binding. 235 | let append = list.append 236 | append(4) 237 | XCTAssertEqual([1, 2, 3, 4], list) 238 | 239 | // Check *args/**kwargs behavior: `str.format(*args, **kwargs)`. 240 | let greeting: PythonObject = "{0} {first} {last}!" 241 | XCTAssertEqual("Hi John Smith!", 242 | greeting.format("Hi", first: "John", last: "Smith")) 243 | XCTAssertEqual("Hey Jane Doe!", 244 | greeting.format("Hey", first: "Jane", last: "Doe")) 245 | } 246 | 247 | func testConvertibleFromPython() { 248 | // Ensure that we cover the -1 case as this is used by Python 249 | // to signal conversion errors. 250 | let minusOne: PythonObject = -1 251 | let zero: PythonObject = 0 252 | let five: PythonObject = 5 253 | let half: PythonObject = 0.5 254 | let string: PythonObject = "abc" 255 | 256 | #if !os(Windows) 257 | XCTAssertEqual(-1, Int(minusOne)) 258 | XCTAssertEqual(-1, Int8(minusOne)) 259 | XCTAssertEqual(-1, Int16(minusOne)) 260 | XCTAssertEqual(-1, Int32(minusOne)) 261 | XCTAssertEqual(-1, Int64(minusOne)) 262 | #endif 263 | XCTAssertEqual(-1.0, Float(minusOne)) 264 | XCTAssertEqual(-1.0, Double(minusOne)) 265 | 266 | XCTAssertEqual(0, Int(zero)) 267 | XCTAssertEqual(0.0, Double(zero)) 268 | 269 | XCTAssertEqual(5, UInt(five)) 270 | XCTAssertEqual(5, UInt8(five)) 271 | XCTAssertEqual(5, UInt16(five)) 272 | XCTAssertEqual(5, UInt32(five)) 273 | XCTAssertEqual(5, UInt64(five)) 274 | XCTAssertEqual(5.0, Float(five)) 275 | XCTAssertEqual(5.0, Double(five)) 276 | 277 | XCTAssertEqual(0.5, Float(half)) 278 | XCTAssertEqual(0.5, Double(half)) 279 | // Python rounds down in this case. 280 | // XCTAssertEqual(0, Int(half)) 281 | 282 | XCTAssertEqual("abc", String(string)) 283 | 284 | XCTAssertNil(String(zero)) 285 | #if !os(Windows) 286 | XCTAssertNil(Int(string)) 287 | #endif 288 | XCTAssertNil(Double(string)) 289 | } 290 | 291 | func testPythonConvertible() { 292 | let minusOne: PythonObject = -1 293 | let five: PythonObject = 5 294 | 295 | XCTAssertEqual(minusOne, Int(-1).pythonObject) 296 | XCTAssertEqual(minusOne, Int8(-1).pythonObject) 297 | XCTAssertEqual(minusOne, Int16(-1).pythonObject) 298 | XCTAssertEqual(minusOne, Int32(-1).pythonObject) 299 | XCTAssertEqual(minusOne, Int64(-1).pythonObject) 300 | XCTAssertEqual(minusOne, Float(-1).pythonObject) 301 | XCTAssertEqual(minusOne, Double(-1).pythonObject) 302 | 303 | XCTAssertEqual(five, UInt(5).pythonObject) 304 | XCTAssertEqual(five, UInt8(5).pythonObject) 305 | XCTAssertEqual(five, UInt16(5).pythonObject) 306 | XCTAssertEqual(five, UInt32(5).pythonObject) 307 | XCTAssertEqual(five, UInt64(5).pythonObject) 308 | XCTAssertEqual(five, Float(5).pythonObject) 309 | XCTAssertEqual(five, Double(5).pythonObject) 310 | } 311 | 312 | // SR-9230: https://bugs.swift.org/browse/SR-9230 313 | func testSR9230() { 314 | let pythonDict = Python.dict(a: "a", b: "b") 315 | XCTAssertEqual(Python.len(pythonDict), 2) 316 | } 317 | 318 | // TF-78: isType() consumed refcount for type objects like `PyBool_Type`. 319 | func testPythonRefCount() { 320 | let b: PythonObject = true 321 | for _ in 0...20 { 322 | // This triggers isType(), which used to crash after repeated invocation 323 | // because of reduced refcount for `PyBool_Type`. 324 | _ = Bool.init(b) 325 | } 326 | } 327 | 328 | func testPythonBytes() { 329 | let bytes = PythonBytes([UInt8(1), UInt8(2), UInt8(3), UInt8(4)]) 330 | bytes.withUnsafeBytes { 331 | XCTAssertEqual(Array($0), [1, 2, 3, 4]) 332 | } 333 | } 334 | 335 | func testPythonBytesInt8() { 336 | let bytes = PythonBytes([Int8(1), Int8(2), Int8(3), Int8(4)]) 337 | bytes.withUnsafeBytes { 338 | XCTAssertEqual(Array($0), [1, 2, 3, 4]) 339 | } 340 | } 341 | 342 | func testPythonBytesNonContiguousSequence() { 343 | let bytes = PythonBytes(CollectionOfOne(UInt8(1))) 344 | bytes.withUnsafeBytes { 345 | XCTAssertEqual(Array($0), [1]) 346 | } 347 | } 348 | 349 | func testPythonBytesNonContiguousSequenceInt8() { 350 | let bytes = PythonBytes(CollectionOfOne(Int8(1))) 351 | bytes.withUnsafeBytes { 352 | XCTAssertEqual(Array($0), [1]) 353 | } 354 | } 355 | 356 | func testBytesConversion() { 357 | let bytes = PythonBytes(CollectionOfOne(UInt8(1))) 358 | let otherBytes = PythonBytes(bytes.pythonObject) 359 | otherBytes?.withUnsafeBytes { 360 | XCTAssertEqual(Array($0), [1]) 361 | } 362 | XCTAssertEqual(bytes, otherBytes) 363 | } 364 | 365 | /// Tests an emulation of the Python `with` statement. 366 | /// 367 | /// Mirrors: 368 | /// ```python 369 | /// with open('temp', 'w') as opened_file: 370 | /// opened_file.write('Contents') 371 | /// with open('temp', 'r') as opened_file: 372 | /// contents = opened_file.read() 373 | /// ``` 374 | func testWith() { 375 | Python.with(Python.open("temp", "w")) { opened_file in 376 | opened_file.write("Contents") 377 | } 378 | Python.with(Python.open("temp", "r")) { opened_file in 379 | let contents = opened_file.read() 380 | XCTAssertEqual("Contents", String(contents)!) 381 | } 382 | } 383 | } 384 | --------------------------------------------------------------------------------