diff --git a/.agents/skills b/.agents/skills
new file mode 120000
index 0000000..f0c855e
--- /dev/null
+++ b/.agents/skills
@@ -0,0 +1 @@
+../Agents/Skills
\ No newline at end of file
diff --git a/.claude/skills b/.claude/skills
new file mode 120000
index 0000000..f0c855e
--- /dev/null
+++ b/.claude/skills
@@ -0,0 +1 @@
+../Agents/Skills
\ No newline at end of file
diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 1b57d86..a05389d 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -1,36 +1,39 @@
name: Continuous Integration
on:
- - push
- - pull_request
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
jobs:
continuous-integration:
+ if: >
+ (github.event_name != 'pull_request' || github.event.pull_request.draft == false) &&
+ !contains(github.event.head_commit.message || '', '[SKIP-CI]') &&
+ !contains(github.event.pull_request.title || '', '[SKIP-CI]')
strategy:
matrix:
os:
- ubuntu-latest
- - macOS-latest
+ - macos-latest
runs-on: ${{ matrix.os }}
steps:
+ - name: Show Swift Version
+ run: swift --version
- uses: actions/checkout@v1
- - name: Installing Swift (Linux)
- if: matrix.os == 'ubuntu-latest'
- env:
- SWIFT_VERSION: 5.1
- run: |
- eval "$(curl -sL https://swiftenv.fuller.li/install.sh)"
- ~/.swiftenv/bin/swiftenv global $SWIFT_VERSION
- sudo ln -s ~/.swiftenv/shims/swift /usr/bin/swift
- - name: Installing Swift (macOS)
- if: matrix.os == 'macOS-latest'
+ - uses: actions/setup-python@v6
+ with:
+ python-version: '3.13'
+ - name: Configure PythonKit
+ shell: bash
run: |
- sudo xcode-select -s /Applications/Xcode_11.app/Contents/Developer
- - name: Test (Python 2)
- env:
- PYTHON_VERSION: 2
- run: swift test --enable-test-discovery
- - name: Test (Python 3)
+ python_library="$(find -L "$pythonLocation" \( -name 'libpython3.13*.dylib' -o -name 'libpython3.13*.so' \) -print -quit)"
+ test -n "$python_library"
+ echo "PYTHON_LIBRARY=$python_library" >> "$GITHUB_ENV"
+ - name: Test
+ run: swift test
env:
- PYTHON_VERSION: 3
- run: swift test --enable-test-discovery
+ PYTHON_LOADER_LOGGING: TRUE
diff --git a/.gitignore b/.gitignore
index 9e8d8ee..3d50865 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,8 +6,8 @@
!.travis.yml
# Swift
-build/
-Package.resolved
+/build/
+/Package.resolved
# Python
*.pyc
@@ -15,6 +15,7 @@ Package.resolved
# Temporary Items
*.tmp
*.tmp.*
+temp
# Virtual Environments
/venv*/
@@ -23,5 +24,10 @@ Package.resolved
*.override.*
# Extra Directories
-/Assets
-/Extra
+/Assets/
+/Extra/
+
+# Xcode
+xcuserdata/
+*.xcscmblueprint
+*.xccheckout
diff --git a/.gitkeep b/.gitkeep
new file mode 100644
index 0000000..5114dca
--- /dev/null
+++ b/.gitkeep
@@ -0,0 +1 @@
+55307D11-9B02-4882-B275-49222602DC5E
diff --git a/.open_source b/.open_source
new file mode 100644
index 0000000..e69de29
diff --git a/.public_project b/.public_project
new file mode 100644
index 0000000..e69de29
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..2538ff9
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,16 @@
+# Agents
+
+This is an open-source Swift package with a public API.
+
+## Project Notes
+
+- Read `README.md` for project-specific architecture, targets, and conventions.
+- Keep project-specific guidance in `README.md` or source-local documentation, not in `AGENTS.md`.
+
+## Public Repository
+
+**IMPORTANT**: This is a public repository. Do not add internal or confidential information, including private project names, internal consumers, local paths, credentials, or content from private repositories or conversations. Use public, self-contained examples and dependencies.
+
+## Public API
+
+Modify this repository with extra care. Preserve its public APIs, behavior, supported platforms, toolchain requirements, and stable URLs. Do not introduce breaking changes without a detailed compatibility review and explicit authorization. Review source, tests, documentation, generated files, agent instructions, and commit or pull-request text for information disclosure before publishing.
diff --git a/Agents/Instructions/.gitkeep b/Agents/Instructions/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/Agents/Skills/.gitkeep b/Agents/Skills/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 120000
index 0000000..47dc3e3
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+AGENTS.md
\ No newline at end of file
diff --git a/Package.swift b/Package.swift
index 064c879..2f5b7f0 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,36 +1,26 @@
-// swift-tools-version:5.0
+// swift-tools-version:6.0
import PackageDescription
let package = Package(
- name: "PythonTool",
+ name: "PythonKit",
products: [
- .executable(
- name: "PythonTool",
- targets: ["PythonTool"]
- ),
.library(
name: "PythonKit",
targets: ["PythonKit"]
)
],
- dependencies: [
- .package(url: "https://github.com/pvieito/CommandLineKit.git", .branch("master")),
- .package(url: "https://github.com/pvieito/LoggerKit.git", .branch("master"))
- ],
targets: [
- .target(
- name: "PythonTool",
- dependencies: ["LoggerKit", "CommandLineKit", "PythonKit"],
- path: "PythonTool"
- ),
.target(
name: "PythonKit",
path: "PythonKit"
),
.testTarget(
name: "PythonKitTests",
- dependencies: ["PythonKit"]
+ dependencies: [
+ "PythonKit",
+ ]
),
- ]
+ ],
+ swiftLanguageModes: [.v5]
)
diff --git a/PythonKit/Info.plist b/PythonKit/Info.plist
deleted file mode 100644
index 1dcdd98..0000000
--- a/PythonKit/Info.plist
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- $(CURRENT_PROJECT_VERSION)
- NSHumanReadableCopyright
- Copyright © 2018 Pedro José Pereira Vieito. All rights reserved.
- NSPrincipalClass
-
-
-
diff --git a/PythonKit/NumpyConversion.swift b/PythonKit/NumpyConversion.swift
index 37b8588..e0c81a2 100644
--- a/PythonKit/NumpyConversion.swift
+++ b/PythonKit/NumpyConversion.swift
@@ -17,142 +17,142 @@
/// The `numpy` Python module.
/// Note: Global variables are lazy, so the following declaration won't produce
-// a Python import error until it is first used.
+/// a Python import error until it is first used.
private let np = Python.import("numpy")
private let ctypes = Python.import("ctypes")
/// A type that can be initialized from a `numpy.ndarray` instance represented
/// as a `PythonObject`.
public protocol ConvertibleFromNumpyArray {
- init?(numpy: PythonObject)
+ init?(numpy: PythonObject)
}
/// A type that is bitwise compatible with one or more NumPy scalar types.
public protocol NumpyScalarCompatible {
- /// The NumPy scalar types that this type is bitwise compatible with. Must
- /// be nonempty.
- static var numpyScalarTypes: [PythonObject] { get }
- /// The Python `ctypes` scalar type corresponding to this type.
- static var ctype: PythonObject { get }
+ /// The NumPy scalar types that this type is bitwise compatible with. Must
+ /// be nonempty.
+ static var numpyScalarTypes: [PythonObject] { get }
+ /// The Python `ctypes` scalar type corresponding to this type.
+ static var ctype: PythonObject { get }
}
extension Bool : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.bool_, Python.bool]
- public static var ctype: PythonObject { return ctypes.c_bool }
+ public static let numpyScalarTypes = [np.bool_, Python.bool]
+ public static var ctype: PythonObject { return ctypes.c_bool }
}
extension UInt8 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.uint8]
- public static var ctype: PythonObject { return ctypes.c_uint8 }
+ public static let numpyScalarTypes = [np.uint8]
+ public static var ctype: PythonObject { return ctypes.c_uint8 }
}
extension Int8 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.int8]
- public static var ctype: PythonObject { return ctypes.c_int8 }
+ public static let numpyScalarTypes = [np.int8]
+ public static var ctype: PythonObject { return ctypes.c_int8 }
}
extension UInt16 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.uint16]
- public static var ctype: PythonObject { return ctypes.c_uint16 }
+ public static let numpyScalarTypes = [np.uint16]
+ public static var ctype: PythonObject { return ctypes.c_uint16 }
}
extension Int16 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.int16]
- public static var ctype: PythonObject { return ctypes.c_int16 }
+ public static let numpyScalarTypes = [np.int16]
+ public static var ctype: PythonObject { return ctypes.c_int16 }
}
extension UInt32 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.uint32]
- public static var ctype: PythonObject { return ctypes.c_uint32 }
+ public static let numpyScalarTypes = [np.uint32]
+ public static var ctype: PythonObject { return ctypes.c_uint32 }
}
extension Int32 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.int32]
- public static var ctype: PythonObject { return ctypes.c_int32 }
+ public static let numpyScalarTypes = [np.int32]
+ public static var ctype: PythonObject { return ctypes.c_int32 }
}
extension UInt64 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.uint64]
- public static var ctype: PythonObject { return ctypes.c_uint64 }
+ public static let numpyScalarTypes = [np.uint64]
+ public static var ctype: PythonObject { return ctypes.c_uint64 }
}
extension Int64 : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.int64]
- public static var ctype: PythonObject { return ctypes.c_int64 }
+ public static let numpyScalarTypes = [np.int64]
+ public static var ctype: PythonObject { return ctypes.c_int64 }
}
extension Float : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.float32]
- public static var ctype: PythonObject { return ctypes.c_float }
+ public static let numpyScalarTypes = [np.float32]
+ public static var ctype: PythonObject { return ctypes.c_float }
}
extension Double : NumpyScalarCompatible {
- public static let numpyScalarTypes = [np.float64]
- public static var ctype: PythonObject { return ctypes.c_double }
+ public static let numpyScalarTypes = [np.float64]
+ public static var ctype: PythonObject { return ctypes.c_double }
}
extension Array : ConvertibleFromNumpyArray
- where Element : NumpyScalarCompatible {
- /// Creates an `Array` with the same shape and scalars as the specified
- /// `numpy.ndarray` instance.
- ///
- /// - Parameter numpyArray: The `numpy.ndarray` instance to convert.
- /// - Precondition: The `numpy` Python package must be installed.
- /// - Returns: `numpyArray` converted to an `Array`. Returns `nil` if
- /// `numpyArray` is not 1-D or does not have a compatible scalar `dtype`.
- public init?(numpy numpyArray: PythonObject) {
- // Check if input is a `numpy.ndarray` instance.
- guard Python.isinstance(numpyArray, np.ndarray) == true else {
- return nil
+where Element : NumpyScalarCompatible {
+ /// Creates an `Array` with the same shape and scalars as the specified
+ /// `numpy.ndarray` instance.
+ ///
+ /// - Parameter numpyArray: The `numpy.ndarray` instance to convert.
+ /// - Precondition: The `numpy` Python package must be installed.
+ /// - Returns: `numpyArray` converted to an `Array`. Returns `nil` if
+ /// `numpyArray` is not 1-D or does not have a compatible scalar `dtype`.
+ public init?(numpy numpyArray: PythonObject) {
+ // Check if input is a `numpy.ndarray` instance.
+ guard Python.isinstance(numpyArray, np.ndarray) == true else {
+ return nil
+ }
+ // Check if the dtype of the `ndarray` is compatible with the `Element`
+ // type.
+ guard Element.numpyScalarTypes.contains(numpyArray.dtype) else {
+ return nil
+ }
+
+ // Only 1-D `ndarray` instances can be converted to `Array`.
+ let pyShape = numpyArray.__array_interface__["shape"]
+ guard let shape = Array(pyShape) else { return nil }
+ guard shape.count == 1 else {
+ return nil
+ }
+
+ // Make sure that the array is contiguous in memory. This does a copy if
+ // the array is not already contiguous in memory.
+ let contiguousNumpyArray = np.ascontiguousarray(numpyArray)
+
+ guard let ptrVal =
+ UInt(contiguousNumpyArray.__array_interface__["data"].tuple2.0) else {
+ return nil
+ }
+ guard let ptr = UnsafePointer(bitPattern: ptrVal) else {
+ fatalError("numpy.ndarray data pointer was nil")
+ }
+ // This code avoids constructing and initialize from `UnsafeBufferPointer`
+ // because that uses the `init(_ elements: S)` initializer,
+ // which performs unnecessary copying.
+ let dummyPointer = UnsafeMutablePointer.allocate(capacity: 1)
+ let scalarCount = shape.reduce(1, *)
+ self.init(repeating: dummyPointer.move(), count: scalarCount)
+ dummyPointer.deallocate()
+ withUnsafeMutableBufferPointer { buffPtr in
+ buffPtr.baseAddress!.update(from: ptr, count: scalarCount)
+ }
}
- // Check if the dtype of the `ndarray` is compatible with the `Element`
- // type.
- guard Element.numpyScalarTypes.contains(numpyArray.dtype) else {
- return nil
- }
-
- // Only 1-D `ndarray` instances can be converted to `Array`.
- let pyShape = numpyArray.__array_interface__["shape"]
- guard let shape = Array(pyShape) else { return nil }
- guard shape.count == 1 else {
- return nil
- }
-
- // Make sure that the array is contiguous in memory. This does a copy if
- // the array is not already contiguous in memory.
- let contiguousNumpyArray = np.ascontiguousarray(numpyArray)
-
- guard let ptrVal =
- UInt(contiguousNumpyArray.__array_interface__["data"].tuple2.0) else {
- return nil
- }
- guard let ptr = UnsafePointer(bitPattern: ptrVal) else {
- fatalError("numpy.ndarray data pointer was nil")
- }
- // This code avoids constructing and initialize from `UnsafeBufferPointer`
- // because that uses the `init(_ elements: S)` initializer,
- // which performs unnecessary copying.
- let dummyPointer = UnsafeMutablePointer.allocate(capacity: 1)
- let scalarCount = shape.reduce(1, *)
- self.init(repeating: dummyPointer.move(), count: scalarCount)
- dummyPointer.deallocate()
- withUnsafeMutableBufferPointer { buffPtr in
- buffPtr.baseAddress!.assign(from: ptr, count: scalarCount)
- }
- }
}
public extension Array where Element : NumpyScalarCompatible {
- /// Creates a 1-D `numpy.ndarray` instance with the same scalars as this
- /// `Array`.
- ///
- /// - Precondition: The `numpy` Python package must be installed.
- func makeNumpyArray() -> PythonObject {
- return withUnsafeBytes { bytes in
- let data = ctypes.cast(Int(bitPattern: bytes.baseAddress),
- ctypes.POINTER(Element.ctype))
- let ndarray = np.ctypeslib.as_array(data, shape: PythonObject(tupleOf: count))
- return np.copy(ndarray)
+ /// Creates a 1-D `numpy.ndarray` instance with the same scalars as this
+ /// `Array`.
+ ///
+ /// - Precondition: The `numpy` Python package must be installed.
+ func makeNumpyArray() -> PythonObject {
+ return withUnsafeBytes { bytes in
+ let data = ctypes.cast(Int(bitPattern: bytes.baseAddress),
+ ctypes.POINTER(Element.ctype))
+ let ndarray = np.ctypeslib.as_array(data, shape: PythonObject(tupleOf: count))
+ return np.copy(ndarray)
+ }
}
- }
}
diff --git a/PythonKit/Python.swift b/PythonKit/Python.swift
index 9b96916..35d2fcb 100644
--- a/PythonKit/Python.swift
+++ b/PythonKit/Python.swift
@@ -25,6 +25,7 @@
/// Typealias used when passing or returning a `PyObject` pointer with
/// implied ownership.
+@usableFromInline
typealias OwnedPyObjectPointer = PyObjectPointer
/// A primitive reference to a Python C API `PyObject`.
@@ -37,34 +38,34 @@ typealias OwnedPyObjectPointer = PyObjectPointer
// implement move semantics.
@usableFromInline @_fixed_layout
final class PyReference {
- private var pointer: OwnedPyObjectPointer
-
- // This `PyReference`, once deleted, will make no delta change to the
- // python object's reference count. It will however, retain the reference for
- // the lifespan of this object.
- init(_ pointer: OwnedPyObjectPointer) {
- self.pointer = pointer
- Py_IncRef(pointer)
- }
+ private var pointer: OwnedPyObjectPointer
+
+ // This `PyReference`, once deleted, will make no delta change to the
+ // python object's reference count. It will however, retain the reference for
+ // the lifespan of this object.
+ init(_ pointer: OwnedPyObjectPointer) {
+ self.pointer = pointer
+ Py_IncRef(pointer)
+ }
- // This `PyReference` adopts the +1 reference and will decrement it in the
- // future.
- init(consuming pointer: PyObjectPointer) {
- self.pointer = pointer
- }
+ // This `PyReference` adopts the +1 reference and will decrement it in the
+ // future.
+ init(consuming pointer: PyObjectPointer) {
+ self.pointer = pointer
+ }
- deinit {
- Py_DecRef(pointer)
- }
+ deinit {
+ Py_DecRef(pointer)
+ }
- var borrowedPyObject: PyObjectPointer {
- return pointer
- }
+ var borrowedPyObject: PyObjectPointer {
+ return pointer
+ }
- var ownedPyObject: OwnedPyObjectPointer {
- Py_IncRef(pointer)
- return pointer
- }
+ var ownedPyObject: OwnedPyObjectPointer {
+ Py_IncRef(pointer)
+ return pointer
+ }
}
//===----------------------------------------------------------------------===//
@@ -87,58 +88,58 @@ final class PyReference {
@dynamicCallable
@dynamicMemberLookup
public struct PythonObject {
- /// The underlying `PyReference`.
- fileprivate var reference: PyReference
+ /// The underlying `PyReference`.
+ fileprivate var reference: PyReference
- @usableFromInline
- init(_ pointer: PyReference) {
- reference = pointer
- }
+ @usableFromInline
+ init(_ pointer: PyReference) {
+ reference = pointer
+ }
- /// Creates a new instance and a new reference.
- init(_ pointer: OwnedPyObjectPointer) {
- reference = PyReference(pointer)
- }
+ /// Creates a new instance and a new reference.
+ init(_ pointer: OwnedPyObjectPointer) {
+ reference = PyReference(pointer)
+ }
- /// Creates a new instance consuming the specified `PyObject` pointer.
- init(consuming pointer: PyObjectPointer) {
- reference = PyReference(consuming: pointer)
- }
+ /// Creates a new instance consuming the specified `PyObject` pointer.
+ init(consuming pointer: PyObjectPointer) {
+ reference = PyReference(consuming: pointer)
+ }
- fileprivate var borrowedPyObject: PyObjectPointer {
- return reference.borrowedPyObject
- }
+ fileprivate var borrowedPyObject: PyObjectPointer {
+ return reference.borrowedPyObject
+ }
- fileprivate var ownedPyObject: OwnedPyObjectPointer {
- return reference.ownedPyObject
- }
+ fileprivate var ownedPyObject: OwnedPyObjectPointer {
+ return reference.ownedPyObject
+ }
}
// Make `print(python)` print a pretty form of the `PythonObject`.
extension PythonObject : CustomStringConvertible {
- /// A textual description of this `PythonObject`, produced by `Python.str`.
- public var description: String {
- // The `str` function is used here because it is designed to return
- // human-readable descriptions of Python objects. The Python REPL also uses
- // it for printing descriptions.
- // `repr` is not used because it is not designed to be readable and takes
- // too long for large objects.
- return String(Python.str(self))!
- }
+ /// A textual description of this `PythonObject`, produced by `Python.str`.
+ public var description: String {
+ // The `str` function is used here because it is designed to return
+ // human-readable descriptions of Python objects. The Python REPL also uses
+ // it for printing descriptions.
+ // `repr` is not used because it is not designed to be readable and takes
+ // too long for large objects.
+ return String(Python.str(self))!
+ }
}
// Make `PythonObject` show up nicely in the Xcode Playground results sidebar.
extension PythonObject : CustomPlaygroundDisplayConvertible {
- public var playgroundDescription: Any {
- return description
- }
+ public var playgroundDescription: Any {
+ return description
+ }
}
// Mirror representation, used by debugger/REPL.
extension PythonObject : CustomReflectable {
- public var customMirror: Mirror {
- return Mirror(self, children: [], displayStyle: .struct)
- }
+ public var customMirror: Mirror {
+ return Mirror(self, children: [], displayStyle: .struct)
+ }
}
//===----------------------------------------------------------------------===//
@@ -147,27 +148,27 @@ extension PythonObject : CustomReflectable {
/// A type whose values can be converted to a `PythonObject`.
public protocol PythonConvertible {
- /// A `PythonObject` instance representing this value.
- var pythonObject: PythonObject { get }
+ /// A `PythonObject` instance representing this value.
+ var pythonObject: PythonObject { get }
}
public extension PythonObject {
- /// Creates a new instance from a `PythonConvertible` value.
- init(_ object: T) {
- self.init(object.pythonObject)
- }
+ /// Creates a new instance from a `PythonConvertible` value.
+ init(_ object: T) {
+ self.init(object.pythonObject)
+ }
}
/// Internal helpers to convert `PythonConvertible` values to owned and borrowed
/// `PyObject` instances. These should not be made public.
fileprivate extension PythonConvertible {
- var borrowedPyObject: PyObjectPointer {
- return pythonObject.borrowedPyObject
- }
+ var borrowedPyObject: PyObjectPointer {
+ return pythonObject.borrowedPyObject
+ }
- var ownedPyObject: OwnedPyObjectPointer {
- return pythonObject.ownedPyObject
- }
+ var ownedPyObject: OwnedPyObjectPointer {
+ return pythonObject.ownedPyObject
+ }
}
//===----------------------------------------------------------------------===//
@@ -176,20 +177,20 @@ fileprivate extension PythonConvertible {
/// A type that can be initialized from a `PythonObject`.
public protocol ConvertibleFromPython {
- /// Creates a new instance from the given `PythonObject`, if possible.
- /// - Note: Conversion may fail if the given `PythonObject` instance is
- /// incompatible (e.g. a Python `string` object cannot be converted into an
- /// `Int`).
- init?(_ object: PythonObject)
+ /// Creates a new instance from the given `PythonObject`, if possible.
+ /// - Note: Conversion may fail if the given `PythonObject` instance is
+ /// incompatible (e.g. a Python `string` object cannot be converted into an
+ /// `Int`).
+ init?(_ object: PythonObject)
}
// `PythonObject` is trivially `PythonConvertible`.
extension PythonObject : PythonConvertible, ConvertibleFromPython {
- public init(_ object: PythonObject) {
- self.init(consuming: object.ownedPyObject)
- }
+ public init(_ object: PythonObject) {
+ self.init(consuming: object.ownedPyObject)
+ }
- public var pythonObject: PythonObject { return self }
+ public var pythonObject: PythonObject { return self }
}
//===----------------------------------------------------------------------===//
@@ -197,68 +198,68 @@ extension PythonObject : PythonConvertible, ConvertibleFromPython {
//===----------------------------------------------------------------------===//
public extension PythonObject {
- /// Returns a callable version of this `PythonObject`. When called, the result
- /// throws a Swift error if the underlying Python function throws a Python
- /// exception.
- var throwing: ThrowingPythonObject {
- return ThrowingPythonObject(self)
- }
+ /// Returns a callable version of this `PythonObject`. When called, the result
+ /// throws a Swift error if the underlying Python function throws a Python
+ /// exception.
+ var throwing: ThrowingPythonObject {
+ return ThrowingPythonObject(self)
+ }
}
/// An error produced by a failable Python operation.
public enum PythonError : Error, Equatable {
- /// A Python runtime exception, produced by calling a Python function.
- case exception(PythonObject, traceback: PythonObject?)
-
- /// A failed call on a `PythonObject`.
- /// Reasons for failure include:
- /// - A non-callable Python object was called.
- /// - An incorrect number of arguments were provided to the callable Python
- /// object.
- /// - An invalid keyword argument was specified.
- case invalidCall(PythonObject)
-
- /// A module import error.
- case invalidModule(String)
+ /// A Python runtime exception, produced by calling a Python function.
+ case exception(PythonObject, traceback: PythonObject?)
+
+ /// A failed call on a `PythonObject`.
+ /// Reasons for failure include:
+ /// - A non-callable Python object was called.
+ /// - An incorrect number of arguments were provided to the callable Python
+ /// object.
+ /// - An invalid keyword argument was specified.
+ case invalidCall(PythonObject)
+
+ /// A module import error.
+ case invalidModule(String)
}
extension PythonError : CustomStringConvertible {
- public var description: String {
- switch self {
- case .exception(let e, let t):
- var exceptionDescription = "Python exception: \(e)"
- if let t = t {
- let traceback = Python.import("traceback")
- exceptionDescription += """
- \nTraceback:
- \(PythonObject("").join(traceback.format_tb(t)))
- """
- }
- return exceptionDescription
- case .invalidCall(let e):
- return "Invalid Python call: \(e)"
- case .invalidModule(let m):
- return "Invalid Python module: \(m)"
- }
- }
+ public var description: String {
+ switch self {
+ case .exception(let e, let t):
+ var exceptionDescription = "Python exception: \(e)"
+ if let t = t {
+ let traceback = Python.import("traceback")
+ exceptionDescription += """
+ \nTraceback:
+ \(PythonObject("").join(traceback.format_tb(t)))
+ """
+ }
+ return exceptionDescription
+ case .invalidCall(let e):
+ return "Invalid Python call: \(e)"
+ case .invalidModule(let m):
+ return "Invalid Python module: \(m)"
+ }
+ }
}
// Reflect a Python error (which must be active) into a Swift error if one is
// active.
private func throwPythonErrorIfPresent() throws {
- if PyErr_Occurred() == nil { return }
+ if PyErr_Occurred() == nil { return }
- var type: PyObjectPointer?
- var value: PyObjectPointer?
- var traceback: PyObjectPointer?
+ var type: PyObjectPointer?
+ var value: PyObjectPointer?
+ var traceback: PyObjectPointer?
- // Fetch the exception and clear the exception state.
- PyErr_Fetch(&type, &value, &traceback)
+ // Fetch the exception and clear the exception state.
+ PyErr_Fetch(&type, &value, &traceback)
- // The value for the exception may not be set but the type always should be.
- let resultObject = PythonObject(consuming: value ?? type!)
- let tracebackObject = traceback.flatMap { PythonObject(consuming: $0) }
- throw PythonError.exception(resultObject, traceback: tracebackObject)
+ // The value for the exception may not be set but the type always should be.
+ let resultObject = PythonObject(consuming: value ?? type!)
+ let tracebackObject = traceback.flatMap { PythonObject(consuming: $0) }
+ throw PythonError.exception(resultObject, traceback: tracebackObject)
}
/// A `PythonObject` wrapper that enables throwing method calls.
@@ -269,133 +270,149 @@ private func throwPythonErrorIfPresent() throws {
/// `x.throwing(arg1, arg2, ...)`. The methods will still be named
/// `dynamicallyCall` until further discussion/design.
public struct ThrowingPythonObject {
- private var base: PythonObject
-
- fileprivate init(_ base: PythonObject) {
- self.base = base
- }
-
- /// Call `self` with the specified positional arguments.
- /// If the call fails for some reason, `PythonError.invalidCall` is thrown.
- /// - Precondition: `self` must be a Python callable.
- /// - Parameter args: Positional arguments for the Python callable.
- @discardableResult
- public func dynamicallyCall(
- withArguments args: PythonConvertible...
- ) throws -> PythonObject {
- return try dynamicallyCall(withArguments: args)
- }
-
- /// Call `self` with the specified positional arguments.
- /// If the call fails for some reason, `PythonError.invalidCall` is thrown.
- /// - Precondition: `self` must be a Python callable.
- /// - Parameter args: Positional arguments for the Python callable.
- @discardableResult
- public func dynamicallyCall(
- withArguments args: [PythonConvertible] = []
- ) throws -> PythonObject {
- try throwPythonErrorIfPresent()
-
- // Positional arguments are passed as a tuple of objects.
- let argTuple = pyTuple(args.map { $0.pythonObject })
- defer { Py_DecRef(argTuple) }
-
- // Python calls always return a non-null object when successful. If the
- // Python function produces the equivalent of C `void`, it returns the
- // `None` object. A `null` result of `PyObjectCall` happens when there is an
- // error, like `self` not being a Python callable.
- let selfObject = base.ownedPyObject
- defer { Py_DecRef(selfObject) }
-
- guard let result = PyObject_CallObject(selfObject, argTuple) else {
- // If a Python exception was thrown, throw a corresponding Swift error.
- try throwPythonErrorIfPresent()
- throw PythonError.invalidCall(base)
- }
- return PythonObject(consuming: result)
- }
-
- /// Call `self` with the specified arguments.
- /// If the call fails for some reason, `PythonError.invalidCall` is thrown.
- /// - Precondition: `self` must be a Python callable.
- /// - Parameter args: Positional or keyword arguments for the Python callable.
- @discardableResult
- public func dynamicallyCall(
- withKeywordArguments args:
- KeyValuePairs = [:]
- ) throws -> PythonObject {
- try throwPythonErrorIfPresent()
-
- // An array containing positional arguments.
- var positionalArgs: [PythonObject] = []
- // A dictionary object for storing keyword arguments, if any exist.
- var kwdictObject: OwnedPyObjectPointer? = nil
-
- for (key, value) in args {
- if key.isEmpty {
- positionalArgs.append(value.pythonObject)
- continue
- }
- // Initialize dictionary object if necessary.
- if kwdictObject == nil { kwdictObject = PyDict_New()! }
- // Add key-value pair to the dictionary object.
- // TODO: Handle duplicate keys.
- // In Python, `SyntaxError: keyword argument repeated` is thrown.
- let k = PythonObject(key).ownedPyObject
- let v = value.ownedPyObject
- PyDict_SetItem(kwdictObject, k, v)
- Py_DecRef(k)
- Py_DecRef(v)
- }
-
- defer { Py_DecRef(kwdictObject) } // Py_DecRef is `nil` safe.
-
- // Positional arguments are passed as a tuple of objects.
- let argTuple = pyTuple(positionalArgs)
- defer { Py_DecRef(argTuple) }
-
- // Python calls always return a non-null object when successful. If the
- // Python function produces the equivalent of C `void`, it returns the
- // `None` object. A `null` result of `PyObjectCall` happens when there is an
- // error, like `self` not being a Python callable.
- let selfObject = base.ownedPyObject
- defer { Py_DecRef(selfObject) }
-
- guard let result = PyObject_Call(selfObject, argTuple, kwdictObject) else {
- // If a Python exception was thrown, throw a corresponding Swift error.
- try throwPythonErrorIfPresent()
- throw PythonError.invalidCall(base)
- }
- return PythonObject(consuming: result)
- }
-
- /// Converts to a 2-tuple, if possible.
- public var tuple2: (PythonObject, PythonObject)? {
- let ct = base.checking
- guard let elt0 = ct[0], let elt1 = ct[1] else {
- return nil
- }
- return (elt0, elt1)
- }
-
- /// Converts to a 3-tuple, if possible.
- public var tuple3: (PythonObject, PythonObject, PythonObject)? {
- let ct = base.checking
- guard let elt0 = ct[0], let elt1 = ct[1], let elt2 = ct[2] else {
- return nil
- }
- return (elt0, elt1, elt2)
- }
-
- /// Converts to a 4-tuple, if possible.
- public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? {
- let ct = base.checking
- guard let elt0 = ct[0], let elt1 = ct[1],
- let elt2 = ct[2], let elt3 = ct[3] else {
- return nil
- }
- return (elt0, elt1, elt2, elt3)
- }
+ private var base: PythonObject
+
+ fileprivate init(_ base: PythonObject) {
+ self.base = base
+ }
+
+ /// Call `self` with the specified positional arguments.
+ /// If the call fails for some reason, `PythonError.invalidCall` is thrown.
+ /// - Precondition: `self` must be a Python callable.
+ /// - Parameter args: Positional arguments for the Python callable.
+ @discardableResult
+ public func dynamicallyCall(
+ withArguments args: PythonConvertible...) throws -> PythonObject {
+ return try dynamicallyCall(withArguments: args)
+ }
+
+ /// Call `self` with the specified positional arguments.
+ /// If the call fails for some reason, `PythonError.invalidCall` is thrown.
+ /// - Precondition: `self` must be a Python callable.
+ /// - Parameter args: Positional arguments for the Python callable.
+ @discardableResult
+ public func dynamicallyCall(
+ withArguments args: [PythonConvertible] = []) throws -> PythonObject {
+ try throwPythonErrorIfPresent()
+
+ // Positional arguments are passed as a tuple of objects.
+ let argTuple = pyTuple(args.map { $0.pythonObject })
+ defer { Py_DecRef(argTuple) }
+
+ // Python calls always return a non-null object when successful. If the
+ // Python function produces the equivalent of C `void`, it returns the
+ // `None` object. A `null` result of `PyObjectCall` happens when there is an
+ // error, like `self` not being a Python callable.
+ let selfObject = base.ownedPyObject
+ defer { Py_DecRef(selfObject) }
+
+ guard let result = PyObject_CallObject(selfObject, argTuple) else {
+ // If a Python exception was thrown, throw a corresponding Swift error.
+ try throwPythonErrorIfPresent()
+ throw PythonError.invalidCall(base)
+ }
+ return PythonObject(consuming: result)
+ }
+
+ /// Call `self` with the specified arguments.
+ /// If the call fails for some reason, `PythonError.invalidCall` is thrown.
+ /// - Precondition: `self` must be a Python callable.
+ /// - Parameter args: Positional or keyword arguments for the Python callable.
+ @discardableResult
+ public func dynamicallyCall(
+ withKeywordArguments args:
+ KeyValuePairs = [:]) throws -> PythonObject {
+ return try _dynamicallyCall(args)
+ }
+
+ /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal.
+ /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it.
+ @discardableResult
+ public func dynamicallyCall(
+ withKeywordArguments args:
+ [(key: String, value: PythonConvertible)] = []) throws -> PythonObject {
+ return try _dynamicallyCall(args)
+ }
+
+ /// Implementation of `dynamicallyCall(withKeywordArguments)`.
+ private func _dynamicallyCall(_ args: T) throws -> PythonObject
+ where T.Element == (key: String, value: PythonConvertible) {
+ try throwPythonErrorIfPresent()
+
+ // An array containing positional arguments.
+ var positionalArgs: [PythonObject] = []
+ // A dictionary object for storing keyword arguments, if any exist.
+ var kwdictObject: OwnedPyObjectPointer? = nil
+
+ for (key, value) in args {
+ if key.isEmpty {
+ positionalArgs.append(value.pythonObject)
+ continue
+ }
+ // Initialize dictionary object if necessary.
+ if kwdictObject == nil { kwdictObject = PyDict_New()! }
+ // Add key-value pair to the dictionary object.
+ // TODO: Handle duplicate keys.
+ // In Python, `SyntaxError: keyword argument repeated` is thrown.
+ let k = PythonObject(key).ownedPyObject
+ let v = value.ownedPyObject
+ PyDict_SetItem(kwdictObject, k, v)
+ Py_DecRef(k)
+ Py_DecRef(v)
+ }
+
+ defer { Py_DecRef(kwdictObject) } // Py_DecRef is `nil` safe.
+
+ // Positional arguments are passed as a tuple of objects.
+ let argTuple = pyTuple(positionalArgs)
+ defer { Py_DecRef(argTuple) }
+
+ // Python calls always return a non-null object when successful. If the
+ // Python function produces the equivalent of C `void`, it returns the
+ // `None` object. A `null` result of `PyObjectCall` happens when there is an
+ // error, like `self` not being a Python callable.
+ let selfObject = base.ownedPyObject
+ defer { Py_DecRef(selfObject) }
+
+ guard let result = PyObject_Call(selfObject, argTuple, kwdictObject) else {
+ // If a Python exception was thrown, throw a corresponding Swift error.
+ try throwPythonErrorIfPresent()
+ throw PythonError.invalidCall(base)
+ }
+ return PythonObject(consuming: result)
+ }
+
+ /// Converts to a 2-tuple, if possible.
+ public var tuple2: (PythonObject, PythonObject)? {
+ let ct = base.checking
+ guard let elt0 = ct[0], let elt1 = ct[1] else {
+ return nil
+ }
+ return (elt0, elt1)
+ }
+
+ /// Converts to a 3-tuple, if possible.
+ public var tuple3: (PythonObject, PythonObject, PythonObject)? {
+ let ct = base.checking
+ guard let elt0 = ct[0], let elt1 = ct[1], let elt2 = ct[2] else {
+ return nil
+ }
+ return (elt0, elt1, elt2)
+ }
+
+ /// Converts to a 4-tuple, if possible.
+ public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? {
+ let ct = base.checking
+ guard let elt0 = ct[0], let elt1 = ct[1],
+ let elt2 = ct[2], let elt3 = ct[3] else {
+ return nil
+ }
+ return (elt0, elt1, elt2, elt3)
+ }
+
+ public var count: Int? {
+ base.checking.count
+ }
}
@@ -404,10 +421,10 @@ public struct ThrowingPythonObject {
//===----------------------------------------------------------------------===//
public extension PythonObject {
- /// Returns a `PythonObject` wrapper capable of member accesses.
- var checking: CheckingPythonObject {
- return CheckingPythonObject(self)
- }
+ /// Returns a `PythonObject` wrapper capable of member accesses.
+ var checking: CheckingPythonObject {
+ return CheckingPythonObject(self)
+ }
}
/// A `PythonObject` wrapper that enables member accesses.
@@ -415,100 +432,104 @@ public extension PythonObject {
/// fails, `nil` is returned.
@dynamicMemberLookup
public struct CheckingPythonObject {
- /// The underlying `PythonObject`.
- private var base: PythonObject
-
- fileprivate init(_ base: PythonObject) {
- self.base = base
- }
-
- public subscript(dynamicMember name: String) -> PythonObject? {
- get {
- let selfObject = base.ownedPyObject
- defer { Py_DecRef(selfObject) }
- guard let result = PyObject_GetAttrString(selfObject, name) else {
- PyErr_Clear()
- return nil
- }
- // `PyObject_GetAttrString` returns +1 result.
- return PythonObject(consuming: result)
- }
- }
-
- /// Access the element corresponding to the specified `PythonConvertible`
- /// values representing a key.
- /// - Note: This is equivalent to `object[key]` in Python.
- public subscript(key: [PythonConvertible]) -> PythonObject? {
- get {
- let keyObject = flattenedSubscriptIndices(key)
- let selfObject = base.ownedPyObject
- defer {
- Py_DecRef(keyObject)
- Py_DecRef(selfObject)
- }
-
- // `PyObject_GetItem` returns +1 reference.
- if let result = PyObject_GetItem(selfObject, keyObject) {
- return PythonObject(consuming: result)
- }
- PyErr_Clear()
- return nil
- }
- nonmutating set {
- let keyObject = flattenedSubscriptIndices(key)
- let selfObject = base.ownedPyObject
- defer {
- Py_DecRef(keyObject)
- Py_DecRef(selfObject)
- }
-
- if let newValue = newValue {
- let newValueObject = newValue.ownedPyObject
- PyObject_SetItem(selfObject, keyObject, newValueObject)
- Py_DecRef(newValueObject)
- } else {
- // Assigning `nil` deletes the key, just like Swift dictionaries.
- PyObject_DelItem(selfObject, keyObject)
- }
- }
- }
-
- /// Access the element corresponding to the specified `PythonConvertible`
- /// values representing a key.
- /// - Note: This is equivalent to `object[key]` in Python.
- public subscript(key: PythonConvertible...) -> PythonObject? {
- get {
- return self[key]
- }
- nonmutating set {
- self[key] = newValue
- }
- }
-
- /// Converts to a 2-tuple, if possible.
- public var tuple2: (PythonObject, PythonObject)? {
- guard let elt0 = self[0], let elt1 = self[1] else {
- return nil
- }
- return (elt0, elt1)
- }
-
- /// Converts to a 3-tuple, if possible.
- public var tuple3: (PythonObject, PythonObject, PythonObject)? {
- guard let elt0 = self[0], let elt1 = self[1], let elt2 = self[2] else {
- return nil
- }
- return (elt0, elt1, elt2)
- }
-
- /// Converts to a 4-tuple, if possible.
- public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? {
- guard let elt0 = self[0], let elt1 = self[1],
- let elt2 = self[2], let elt3 = self[3] else {
- return nil
- }
- return (elt0, elt1, elt2, elt3)
- }
+ /// The underlying `PythonObject`.
+ private var base: PythonObject
+
+ fileprivate init(_ base: PythonObject) {
+ self.base = base
+ }
+
+ public subscript(dynamicMember name: String) -> PythonObject? {
+ get {
+ let selfObject = base.ownedPyObject
+ defer { Py_DecRef(selfObject) }
+ guard let result = PyObject_GetAttrString(selfObject, name) else {
+ PyErr_Clear()
+ return nil
+ }
+ // `PyObject_GetAttrString` returns +1 result.
+ return PythonObject(consuming: result)
+ }
+ }
+
+ /// Access the element corresponding to the specified `PythonConvertible`
+ /// values representing a key.
+ /// - Note: This is equivalent to `object[key]` in Python.
+ public subscript(key: [PythonConvertible]) -> PythonObject? {
+ get {
+ let keyObject = flattenedSubscriptIndices(key)
+ let selfObject = base.ownedPyObject
+ defer {
+ Py_DecRef(keyObject)
+ Py_DecRef(selfObject)
+ }
+
+ // `PyObject_GetItem` returns +1 reference.
+ if let result = PyObject_GetItem(selfObject, keyObject) {
+ return PythonObject(consuming: result)
+ }
+ PyErr_Clear()
+ return nil
+ }
+ nonmutating set {
+ let keyObject = flattenedSubscriptIndices(key)
+ let selfObject = base.ownedPyObject
+ defer {
+ Py_DecRef(keyObject)
+ Py_DecRef(selfObject)
+ }
+
+ if let newValue = newValue {
+ let newValueObject = newValue.ownedPyObject
+ PyObject_SetItem(selfObject, keyObject, newValueObject)
+ Py_DecRef(newValueObject)
+ } else {
+ // Assigning `nil` deletes the key, just like Swift dictionaries.
+ PyObject_DelItem(selfObject, keyObject)
+ }
+ }
+ }
+
+ /// Access the element corresponding to the specified `PythonConvertible`
+ /// values representing a key.
+ /// - Note: This is equivalent to `object[key]` in Python.
+ public subscript(key: PythonConvertible...) -> PythonObject? {
+ get {
+ return self[key]
+ }
+ nonmutating set {
+ self[key] = newValue
+ }
+ }
+
+ /// Converts to a 2-tuple, if possible.
+ public var tuple2: (PythonObject, PythonObject)? {
+ guard let elt0 = self[0], let elt1 = self[1] else {
+ return nil
+ }
+ return (elt0, elt1)
+ }
+
+ /// Converts to a 3-tuple, if possible.
+ public var tuple3: (PythonObject, PythonObject, PythonObject)? {
+ guard let elt0 = self[0], let elt1 = self[1], let elt2 = self[2] else {
+ return nil
+ }
+ return (elt0, elt1, elt2)
+ }
+
+ /// Converts to a 4-tuple, if possible.
+ public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? {
+ guard let elt0 = self[0], let elt1 = self[1],
+ let elt2 = self[2], let elt3 = self[3] else {
+ return nil
+ }
+ return (elt0, elt1, elt2, elt3)
+ }
+
+ public var count: Int? {
+ Int(Python.len(base))
+ }
}
//===----------------------------------------------------------------------===//
@@ -518,100 +539,106 @@ public struct CheckingPythonObject {
/// Converts an array of indices into a `PythonObject` representing a flattened
/// index.
private func flattenedSubscriptIndices(
- _ indices: [PythonConvertible]
-) -> OwnedPyObjectPointer {
- if indices.count == 1 {
- return indices[0].ownedPyObject
- }
- return pyTuple(indices.map { $0.pythonObject })
+ _ indices: [PythonConvertible]) -> OwnedPyObjectPointer {
+ if indices.count == 1 {
+ return indices[0].ownedPyObject
+ }
+ return pyTuple(indices.map { $0.pythonObject })
}
public extension PythonObject {
- subscript(dynamicMember memberName: String) -> PythonObject {
- get {
- guard let member = checking[dynamicMember: memberName] else {
- fatalError("Could not access PythonObject member '\(memberName)'")
- }
- return member
- }
- nonmutating set {
- let selfObject = ownedPyObject
- defer { Py_DecRef(selfObject) }
- let valueObject = newValue.ownedPyObject
- defer { Py_DecRef(valueObject) }
-
- if PyObject_SetAttrString(selfObject, memberName, valueObject) == -1 {
- try! throwPythonErrorIfPresent()
- fatalError("""
- Could not set PythonObject member '\(memberName)' to the specified \
- value
- """)
- }
- }
- }
-
- /// Access the element corresponding to the specified `PythonConvertible`
- /// values representing a key.
- /// - Note: This is equivalent to `object[key]` in Python.
- subscript(key: PythonConvertible...) -> PythonObject {
- get {
- guard let item = checking[key] else {
- fatalError("""
- Could not access PythonObject element corresponding to the specified \
- key values: \(key)
- """)
- }
- return item
- }
- nonmutating set {
- checking[key] = newValue
- }
- }
-
- /// Converts to a 2-tuple.
- var tuple2: (PythonObject, PythonObject) {
- guard let result = checking.tuple2 else {
- fatalError("Could not convert PythonObject to a 2-element tuple")
- }
- return result
- }
-
- /// Converts to a 3-tuple.
- var tuple3: (PythonObject, PythonObject, PythonObject) {
- guard let result = checking.tuple3 else {
- fatalError("Could not convert PythonObject to a 3-element tuple")
- }
- return result
- }
-
- /// Converts to a 4-tuple.
- var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject) {
- guard let result = checking.tuple4 else {
- fatalError("Could not convert PythonObject to a 4-element tuple")
- }
- return result
- }
-
- /// Call `self` with the specified positional arguments.
- /// - Precondition: `self` must be a Python callable.
- /// - Parameter args: Positional arguments for the Python callable.
- @discardableResult
- func dynamicallyCall(
- withArguments args: [PythonConvertible] = []
- ) -> PythonObject {
- return try! throwing.dynamicallyCall(withArguments: args)
- }
-
- /// Call `self` with the specified arguments.
- /// - Precondition: `self` must be a Python callable.
- /// - Parameter args: Positional or keyword arguments for the Python callable.
- @discardableResult
- func dynamicallyCall(
- withKeywordArguments args:
- KeyValuePairs = [:]
- ) -> PythonObject {
- return try! throwing.dynamicallyCall(withKeywordArguments: args)
- }
+ subscript(dynamicMember memberName: String) -> PythonObject {
+ get {
+ guard let member = checking[dynamicMember: memberName] else {
+ fatalError("Could not access PythonObject member '\(memberName)'")
+ }
+ return member
+ }
+ nonmutating set {
+ let selfObject = ownedPyObject
+ defer { Py_DecRef(selfObject) }
+ let valueObject = newValue.ownedPyObject
+ defer { Py_DecRef(valueObject) }
+
+ if PyObject_SetAttrString(selfObject, memberName, valueObject) == -1 {
+ try! throwPythonErrorIfPresent()
+ fatalError("""
+ Could not set PythonObject member '\(memberName)' to the specified \
+ value
+ """)
+ }
+ }
+ }
+
+ /// Access the element corresponding to the specified `PythonConvertible`
+ /// values representing a key.
+ /// - Note: This is equivalent to `object[key]` in Python.
+ subscript(key: PythonConvertible...) -> PythonObject {
+ get {
+ guard let item = checking[key] else {
+ fatalError("""
+ Could not access PythonObject element corresponding to the specified \
+ key values: \(key)
+ """)
+ }
+ return item
+ }
+ nonmutating set {
+ checking[key] = newValue
+ }
+ }
+
+ /// Converts to a 2-tuple.
+ var tuple2: (PythonObject, PythonObject) {
+ guard let result = checking.tuple2 else {
+ fatalError("Could not convert PythonObject to a 2-element tuple")
+ }
+ return result
+ }
+
+ /// Converts to a 3-tuple.
+ var tuple3: (PythonObject, PythonObject, PythonObject) {
+ guard let result = checking.tuple3 else {
+ fatalError("Could not convert PythonObject to a 3-element tuple")
+ }
+ return result
+ }
+
+ /// Converts to a 4-tuple.
+ var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject) {
+ guard let result = checking.tuple4 else {
+ fatalError("Could not convert PythonObject to a 4-element tuple")
+ }
+ return result
+ }
+
+ /// Call `self` with the specified positional arguments.
+ /// - Precondition: `self` must be a Python callable.
+ /// - Parameter args: Positional arguments for the Python callable.
+ @discardableResult
+ func dynamicallyCall(
+ withArguments args: [PythonConvertible] = []) -> PythonObject {
+ return try! throwing.dynamicallyCall(withArguments: args)
+ }
+
+ /// Call `self` with the specified arguments.
+ /// - Precondition: `self` must be a Python callable.
+ /// - Parameter args: Positional or keyword arguments for the Python callable.
+ @discardableResult
+ func dynamicallyCall(
+ withKeywordArguments args:
+ KeyValuePairs = [:]) -> PythonObject {
+ return try! throwing.dynamicallyCall(withKeywordArguments: args)
+ }
+
+ /// Alias for the function above that lets the caller dynamically construct the argument list, without using a dictionary literal.
+ /// This function must be called explicitly on a `PythonObject` because `@dynamicCallable` does not recognize it.
+ @discardableResult
+ func dynamicallyCall(
+ withKeywordArguments args:
+ [(key: String, value: PythonConvertible)] = []) -> PythonObject {
+ return try! throwing.dynamicallyCall(withKeywordArguments: args)
+ }
}
//===----------------------------------------------------------------------===//
@@ -644,55 +671,66 @@ public let Python = PythonInterface()
/// called `Python`.
@dynamicMemberLookup
public struct PythonInterface {
- /// A dictionary of the Python builtins.
- public let builtins: PythonObject
-
- init() {
- Py_Initialize() // Initialize Python
- builtins = PythonObject(PyEval_GetBuiltins())
-
- // Runtime Fixes:
- //
- // Some Python modules expect to have at least one argument in `sys.argv`.
- PyRun_SimpleString("import sys; sys.argv = ['']")
- // Some Python modules require `sys.executable` to return the path
- // to the Python interpreter executable. In Darwin, Python 3 returns the
- // main process executable path instead (`CommandLine.arguments[0]`).
- PyRun_SimpleString("""
- import sys
- import os
- if sys.version_info.major == 3 and sys.platform == 'darwin':
- sys.executable = os.path.join(sys.exec_prefix, 'bin', 'python3')
- """)
- }
-
- public func attemptImport(_ name: String) throws -> PythonObject {
- guard let module = PyImport_ImportModule(name) else {
- try throwPythonErrorIfPresent()
- throw PythonError.invalidModule(name)
- }
- return PythonObject(consuming: module)
- }
-
- public func `import`(_ name: String) -> PythonObject {
- return try! attemptImport(name)
- }
-
- public subscript(dynamicMember name: String) -> PythonObject {
- return builtins[name]
- }
-
- // The Python runtime version.
- // Equivalent to `sys.version` in Python.
- public var version: PythonObject {
- return self.import("sys").version
- }
-
- // The Python runtime version information.
- // Equivalent to `sys.version_info` in Python.
- public var versionInfo: PythonObject {
- return self.import("sys").version_info
- }
+ /// A dictionary of the Python builtins.
+ public let builtins: PythonObject
+
+ init() {
+ Py_Initialize() // Initialize Python
+ builtins = PythonObject(PyEval_GetBuiltins())
+
+ // Runtime Fixes:
+ PyRun_SimpleString("""
+ import sys
+ import os
+
+ # Some Python modules expect to have at least one argument in `sys.argv`:
+ sys.argv = [""]
+
+ # Some Python modules require `sys.executable` to return the path
+ # to the Python interpreter executable. In Darwin, Python 3 returns the
+ # main process executable path instead:
+ if sys.version_info.major == 3 and sys.platform == "darwin":
+ executable_name = "python{}.{}".format(sys.version_info.major, sys.version_info.minor)
+ sys.executable = os.path.join(sys.exec_prefix, "bin", executable_name)
+ """)
+ }
+
+ public func attemptImport(_ name: String) throws -> PythonObject {
+ guard let module = PyImport_ImportModule(name) else {
+ try throwPythonErrorIfPresent()
+ throw PythonError.invalidModule(name)
+ }
+ return PythonObject(consuming: module)
+ }
+
+ public func `import`(_ name: String) -> PythonObject {
+ return try! attemptImport(name)
+ }
+
+ public subscript(dynamicMember name: String) -> PythonObject {
+ return builtins[name]
+ }
+
+ // The Python runtime version.
+ // Equivalent to `sys.version` in Python.
+ public var version: PythonObject {
+ return self.import("sys").version
+ }
+
+ // The Python runtime version information.
+ // Equivalent to `sys.version_info` in Python.
+ public var versionInfo: PythonObject {
+ return self.import("sys").version_info
+ }
+
+ /// Emulates a Python `with` statement.
+ /// - Parameter object: A context manager object.
+ /// - Parameter body: A closure to call on the result of `object.__enter__()`.
+ public func with(_ object: PythonObject, _ body: (PythonObject) throws -> Void) rethrows {
+ let yieldValue = object.__enter__()
+ try body(yieldValue)
+ yieldValue.__exit__()
+ }
}
//===----------------------------------------------------------------------===//
@@ -701,32 +739,31 @@ public struct PythonInterface {
// Create a Python tuple object with the specified elements.
private func pyTuple(_ vals: T) -> OwnedPyObjectPointer
- where T.Element : PythonConvertible {
-
- let tuple = PyTuple_New(vals.count)!
- for (index, element) in vals.enumerated() {
- // `PyTuple_SetItem` steals the reference of the object stored.
- PyTuple_SetItem(tuple, index, element.ownedPyObject)
- }
- return tuple
+where T.Element : PythonConvertible {
+ let tuple = PyTuple_New(vals.count)!
+ for (index, element) in vals.enumerated() {
+ // `PyTuple_SetItem` steals the reference of the object stored.
+ PyTuple_SetItem(tuple, index, element.ownedPyObject)
+ }
+ return tuple
}
public extension PythonObject {
- // Tuples require explicit support because tuple types cannot conform to
- // protocols.
- init(tupleOf elements: PythonConvertible...) {
- self.init(tupleContentsOf: elements)
- }
+ // Tuples require explicit support because tuple types cannot conform to
+ // protocols.
+ init(tupleOf elements: PythonConvertible...) {
+ self.init(tupleContentsOf: elements)
+ }
- init(tupleContentsOf elements: T)
+ init(tupleContentsOf elements: T)
where T.Element == PythonConvertible {
- self.init(consuming: pyTuple(elements.map { $0.pythonObject }))
- }
+ self.init(consuming: pyTuple(elements.map { $0.pythonObject }))
+ }
- init(tupleContentsOf elements: T)
+ init(tupleContentsOf elements: T)
where T.Element : PythonConvertible {
- self.init(consuming: pyTuple(elements))
- }
+ self.init(consuming: pyTuple(elements))
+ }
}
//===----------------------------------------------------------------------===//
@@ -737,130 +774,130 @@ public extension PythonObject {
/// type descriptor passed in as 'type'.
private func isType(_ object: PythonObject,
type: PyObjectPointer) -> Bool {
- let typePyRef = PythonObject(type)
+ let typePyRef = PythonObject(type)
- let result = Python.isinstance(object, typePyRef)
+ let result = Python.isinstance(object, typePyRef)
- // We cannot use the normal failable Bool initializer from `PythonObject`
- // here because would cause an infinite loop.
- let pyObject = result.ownedPyObject
- defer { Py_DecRef(pyObject) }
+ // We cannot use the normal failable Bool initializer from `PythonObject`
+ // here because would cause an infinite loop.
+ let pyObject = result.ownedPyObject
+ defer { Py_DecRef(pyObject) }
- // Anything not equal to `Py_ZeroStruct` is truthy.
- return pyObject != _Py_ZeroStruct
+ // Anything not equal to `Py_ZeroStruct` is truthy.
+ return pyObject != _Py_ZeroStruct
}
extension Bool : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard isType(pythonObject, type: PyBool_Type) else { return nil }
+ public init?(_ pythonObject: PythonObject) {
+ guard isType(pythonObject, type: PyBool_Type) else { return nil }
- let pyObject = pythonObject.ownedPyObject
- defer { Py_DecRef(pyObject) }
+ let pyObject = pythonObject.ownedPyObject
+ defer { Py_DecRef(pyObject) }
- self = pyObject == _Py_TrueStruct
- }
+ self = pyObject == _Py_TrueStruct
+ }
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return PythonObject(consuming: PyBool_FromLong(self ? 1 : 0))
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return PythonObject(consuming: PyBool_FromLong(self ? 1 : 0))
+ }
}
extension String : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- let pyObject = pythonObject.ownedPyObject
- defer { Py_DecRef(pyObject) }
-
- guard let cString = PyString_AsString(pyObject) else {
- PyErr_Clear()
- return nil
+ public init?(_ pythonObject: PythonObject) {
+ let pyObject = pythonObject.ownedPyObject
+ defer { Py_DecRef(pyObject) }
+
+ guard let cString = PyString_AsString(pyObject) else {
+ PyErr_Clear()
+ return nil
+ }
+ self.init(cString: cString)
}
- self.init(cString: cString)
- }
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- let v = utf8CString.withUnsafeBufferPointer {
- // 1 is subtracted from the C string length to trim the trailing null
- // character (`\0`).
- PyString_FromStringAndSize($0.baseAddress, $0.count - 1)!
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ let v = utf8CString.withUnsafeBufferPointer {
+ // 1 is subtracted from the C string length to trim the trailing null
+ // character (`\0`).
+ PyString_FromStringAndSize($0.baseAddress, $0.count - 1)!
+ }
+ return PythonObject(consuming: v)
}
- return PythonObject(consuming: v)
- }
}
fileprivate extension PythonObject {
- // Converts a `PythonObject` to the given type by applying the appropriate
- // converter function and checking the error value.
- func converted(
- withError errorValue: T, by converter: (OwnedPyObjectPointer) -> T
- ) -> T? {
- let pyObject = ownedPyObject
- defer { Py_DecRef(pyObject) }
-
- assert(PyErr_Occurred() == nil,
- "Python error occurred somewhere but wasn't handled")
+ // Converts a `PythonObject` to the given type by applying the appropriate
+ // converter function and checking the error value.
+ func converted(
+ withError errorValue: T, by converter: (OwnedPyObjectPointer) -> T
+ ) -> T? {
+ let pyObject = ownedPyObject
+ defer { Py_DecRef(pyObject) }
+
+ assert(PyErr_Occurred() == nil,
+ "Python error occurred somewhere but wasn't handled")
+
+ let value = converter(pyObject)
+ guard value != errorValue || PyErr_Occurred() == nil else {
+ PyErr_Clear()
+ return nil
+ }
+ return value
- let value = converter(pyObject)
- guard value != errorValue || PyErr_Occurred() == nil else {
- PyErr_Clear()
- return nil
}
- return value
-
- }
}
extension Int : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- // `PyInt_AsLong` return -1 and sets an error if the Python object is not
- // integer compatible.
- guard let value = pythonObject.converted(withError: -1,
- by: PyInt_AsLong) else {
- return nil
+ public init?(_ pythonObject: PythonObject) {
+ // `PyInt_AsLong` return -1 and sets an error if the Python object is not
+ // integer compatible.
+ guard let value = pythonObject.converted(
+ withError: -1, by: PyInt_AsLong) else {
+ return nil
+ }
+ self = value
}
- self = value
- }
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return PythonObject(consuming: PyInt_FromLong(self))
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return PythonObject(consuming: PyInt_FromLong(self))
+ }
}
extension UInt : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- // `PyInt_AsUnsignedLongMask` isn't documented as such, but in fact it does
- // return -1 and set an error if the Python object is not integer
- // compatible.
- guard let value = pythonObject.converted(
- withError: ~0, by: PyInt_AsUnsignedLongMask) else {
- return nil
+ public init?(_ pythonObject: PythonObject) {
+ // `PyInt_AsUnsignedLongMask` isn't documented as such, but in fact it does
+ // return -1 and set an error if the Python object is not integer
+ // compatible.
+ guard let value = pythonObject.converted(
+ withError: ~0, by: PyInt_AsUnsignedLongMask) else {
+ return nil
+ }
+ self = value
}
- self = value
- }
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return PythonObject(consuming: PyInt_FromSize_t(self))
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return PythonObject(consuming: PyInt_FromSize_t(self))
+ }
}
extension Double : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- // `PyFloat_AsDouble` return -1 and sets an error if the Python object is
- // not float compatible.
- guard let value = pythonObject.converted(withError: -1,
- by: PyFloat_AsDouble) else {
- return nil
+ public init?(_ pythonObject: PythonObject) {
+ // `PyFloat_AsDouble` return -1 and sets an error if the Python object is
+ // not float compatible.
+ guard let value = pythonObject.converted(
+ withError: -1, by: PyFloat_AsDouble) else {
+ return nil
+ }
+ self = value
}
- self = value
- }
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return PythonObject(consuming: PyFloat_FromDouble(self))
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return PythonObject(consuming: PyFloat_FromDouble(self))
+ }
}
//===----------------------------------------------------------------------===//
@@ -871,104 +908,104 @@ extension Double : PythonConvertible, ConvertibleFromPython {
// implementation.
extension Int8 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = Int(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = Int(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return Int(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return Int(self).pythonObject
+ }
}
extension Int16 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = Int(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = Int(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return Int(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return Int(self).pythonObject
+ }
}
extension Int32 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = Int(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = Int(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return Int(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return Int(self).pythonObject
+ }
}
extension Int64 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = Int(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = Int(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return Int(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return Int(self).pythonObject
+ }
}
extension UInt8 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = UInt(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = UInt(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return UInt(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return UInt(self).pythonObject
+ }
}
extension UInt16 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = UInt(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = UInt(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return UInt(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return UInt(self).pythonObject
+ }
}
extension UInt32 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = UInt(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = UInt(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return UInt(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return UInt(self).pythonObject
+ }
}
extension UInt64 : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let i = UInt(pythonObject) else { return nil }
- self.init(i)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let i = UInt(pythonObject) else { return nil }
+ self.init(i)
+ }
- public var pythonObject: PythonObject {
- return UInt(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return UInt(self).pythonObject
+ }
}
// `Float` is `PythonConvertible` via the `Double` implementation.
extension Float : PythonConvertible, ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard let v = Double(pythonObject) else { return nil }
- self.init(v)
- }
+ public init?(_ pythonObject: PythonObject) {
+ guard let v = Double(pythonObject) else { return nil }
+ self.init(v)
+ }
- public var pythonObject: PythonObject {
- return Double(self).pythonObject
- }
+ public var pythonObject: PythonObject {
+ return Double(self).pythonObject
+ }
}
//===----------------------------------------------------------------------===//
@@ -976,9 +1013,9 @@ extension Float : PythonConvertible, ConvertibleFromPython {
//===----------------------------------------------------------------------===//
extension Optional : PythonConvertible where Wrapped : PythonConvertible {
- public var pythonObject: PythonObject {
- return self?.pythonObject ?? Python.None
- }
+ public var pythonObject: PythonObject {
+ return self?.pythonObject ?? Python.None
+ }
}
//===----------------------------------------------------------------------===//
@@ -986,17 +1023,17 @@ extension Optional : PythonConvertible where Wrapped : PythonConvertible {
//===----------------------------------------------------------------------===//
extension Optional : ConvertibleFromPython
- where Wrapped : ConvertibleFromPython {
- public init?(_ object: PythonObject) {
- if object == Python.None {
- self = .none
- } else {
- guard let converted = Wrapped(object) else {
- return nil
- }
- self = .some(converted)
+where Wrapped : ConvertibleFromPython {
+ public init?(_ object: PythonObject) {
+ if object == Python.None {
+ self = .none
+ } else {
+ guard let converted = Wrapped(object) else {
+ return nil
+ }
+ self = .some(converted)
+ }
}
- }
}
//===----------------------------------------------------------------------===//
@@ -1007,70 +1044,71 @@ extension Optional : ConvertibleFromPython
// `Array` conditionally conforms to `PythonConvertible` if the `Element`
// associated type does.
extension Array : PythonConvertible where Element : PythonConvertible {
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- let list = PyList_New(count)!
- for (index, element) in enumerated() {
- // `PyList_SetItem` steals the reference of the object stored.
- _ = PyList_SetItem(list, index, element.ownedPyObject)
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ let list = PyList_New(count)!
+ for (index, element) in enumerated() {
+ // `PyList_SetItem` steals the reference of the object stored.
+ _ = PyList_SetItem(list, index, element.ownedPyObject)
+ }
+ return PythonObject(consuming: list)
}
- return PythonObject(consuming: list)
- }
}
extension Array : ConvertibleFromPython where Element : ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- self = []
- for elementObject in pythonObject {
- guard let element = Element(elementObject) else { return nil }
- append(element)
+ public init?(_ pythonObject: PythonObject) {
+ self = []
+ for elementObject in pythonObject {
+ guard let element = Element(elementObject) else { return nil }
+ append(element)
+ }
}
- }
}
// `Dictionary` conditionally conforms to `PythonConvertible` if the `Key` and
// `Value` associated types do.
extension Dictionary : PythonConvertible
- where Key : PythonConvertible, Value : PythonConvertible {
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- let dict = PyDict_New()!
- for (key, value) in self {
- let k = key.ownedPyObject
- let v = value.ownedPyObject
- PyDict_SetItem(dict, k, v)
- Py_DecRef(k)
- Py_DecRef(v)
+where Key : PythonConvertible, Value : PythonConvertible {
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ let dict = PyDict_New()!
+ for (key, value) in self {
+ let k = key.ownedPyObject
+ let v = value.ownedPyObject
+ PyDict_SetItem(dict, k, v)
+ Py_DecRef(k)
+ Py_DecRef(v)
+ }
+ return PythonObject(consuming: dict)
}
- return PythonObject(consuming: dict)
- }
}
extension Dictionary : ConvertibleFromPython
- where Key : ConvertibleFromPython, Value : ConvertibleFromPython {
- public init?(_ pythonDict: PythonObject) {
- self = [:]
-
- // Iterate over the Python dictionary, converting its keys and values to
- // Swift `Key` and `Value` pairs.
- var key, value: PyObjectPointer?
- var position: Int = 0
-
- while PyDict_Next(pythonDict.borrowedPyObject,
- &position, &key, &value) != 0 {
- // If any key or value is not convertible to the corresponding Swift
- // type, then the entire dictionary is not convertible.
- if let swiftKey = Key(PythonObject(key!)),
- let swiftValue = Value(PythonObject(value!)) {
- // It is possible that there are duplicate keys after conversion. We
- // silently allow duplicate keys and pick a nondeterministic result if
- // there is a collision.
- self[swiftKey] = swiftValue
- } else {
- return nil
- }
- }
- }
+where Key : ConvertibleFromPython, Value : ConvertibleFromPython {
+ public init?(_ pythonDict: PythonObject) {
+ self = [:]
+
+ // Iterate over the Python dictionary, converting its keys and values to
+ // Swift `Key` and `Value` pairs.
+ var key, value: PyObjectPointer?
+ var position: Int = 0
+
+ while PyDict_Next(
+ pythonDict.borrowedPyObject,
+ &position, &key, &value) != 0 {
+ // If any key or value is not convertible to the corresponding Swift
+ // type, then the entire dictionary is not convertible.
+ if let swiftKey = Key(PythonObject(key!)),
+ let swiftValue = Value(PythonObject(value!)) {
+ // It is possible that there are duplicate keys after conversion. We
+ // silently allow duplicate keys and pick a nondeterministic result if
+ // there is a collision.
+ self[swiftKey] = swiftValue
+ } else {
+ return nil
+ }
+ }
+ }
}
//===----------------------------------------------------------------------===//
@@ -1079,62 +1117,62 @@ extension Dictionary : ConvertibleFromPython
//===----------------------------------------------------------------------===//
extension Range : PythonConvertible where Bound : PythonConvertible {
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return Python.slice(lowerBound, upperBound, Python.None)
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return Python.slice(lowerBound, upperBound, Python.None)
+ }
}
extension Range : ConvertibleFromPython where Bound : ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard isType(pythonObject, type: PySlice_Type) else { return nil }
- guard let lowerBound = Bound(pythonObject.start),
- let upperBound = Bound(pythonObject.stop) else {
- return nil
+ public init?(_ pythonObject: PythonObject) {
+ guard isType(pythonObject, type: PySlice_Type) else { return nil }
+ guard let lowerBound = Bound(pythonObject.start),
+ let upperBound = Bound(pythonObject.stop) else {
+ return nil
+ }
+ guard pythonObject.step == Python.None else { return nil }
+ self.init(uncheckedBounds: (lowerBound, upperBound))
}
- guard pythonObject.step == Python.None else { return nil }
- self.init(uncheckedBounds: (lowerBound, upperBound))
- }
}
extension PartialRangeFrom : PythonConvertible where Bound : PythonConvertible {
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return Python.slice(lowerBound, Python.None, Python.None)
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return Python.slice(lowerBound, Python.None, Python.None)
+ }
}
extension PartialRangeFrom : ConvertibleFromPython
- where Bound : ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard isType(pythonObject, type: PySlice_Type) else { return nil }
- guard let lowerBound = Bound(pythonObject.start) else { return nil }
- guard pythonObject.stop == Python.None,
- pythonObject.step == Python.None else {
- return nil
+where Bound : ConvertibleFromPython {
+ public init?(_ pythonObject: PythonObject) {
+ guard isType(pythonObject, type: PySlice_Type) else { return nil }
+ guard let lowerBound = Bound(pythonObject.start) else { return nil }
+ guard pythonObject.stop == Python.None,
+ pythonObject.step == Python.None else {
+ return nil
+ }
+ self.init(lowerBound)
}
- self.init(lowerBound)
- }
}
extension PartialRangeUpTo : PythonConvertible where Bound : PythonConvertible {
- public var pythonObject: PythonObject {
- _ = Python // Ensure Python is initialized.
- return Python.slice(Python.None, upperBound, Python.None)
- }
+ public var pythonObject: PythonObject {
+ _ = Python // Ensure Python is initialized.
+ return Python.slice(Python.None, upperBound, Python.None)
+ }
}
extension PartialRangeUpTo : ConvertibleFromPython
- where Bound : ConvertibleFromPython {
- public init?(_ pythonObject: PythonObject) {
- guard isType(pythonObject, type: PySlice_Type) else { return nil }
- guard let upperBound = Bound(pythonObject.stop) else { return nil }
- guard pythonObject.start == Python.None,
- pythonObject.step == Python.None else {
- return nil
+where Bound : ConvertibleFromPython {
+ public init?(_ pythonObject: PythonObject) {
+ guard isType(pythonObject, type: PySlice_Type) else { return nil }
+ guard let upperBound = Bound(pythonObject.stop) else { return nil }
+ guard pythonObject.start == Python.None,
+ pythonObject.step == Python.None else {
+ return nil
+ }
+ self.init(upperBound)
}
- self.init(upperBound)
- }
}
//===----------------------------------------------------------------------===//
@@ -1142,210 +1180,755 @@ extension PartialRangeUpTo : ConvertibleFromPython
//===----------------------------------------------------------------------===//
private typealias PythonBinaryOp =
- (OwnedPyObjectPointer?, OwnedPyObjectPointer?) -> OwnedPyObjectPointer?
+ (OwnedPyObjectPointer?, OwnedPyObjectPointer?) -> OwnedPyObjectPointer?
+private typealias PythonUnaryOp =
+ (OwnedPyObjectPointer?) -> OwnedPyObjectPointer?
private func performBinaryOp(
- _ op: PythonBinaryOp, lhs: PythonObject, rhs: PythonObject
-) -> PythonObject {
- let result = op(lhs.borrowedPyObject, rhs.borrowedPyObject)
- // If binary operation fails (e.g. due to `TypeError`), throw an exception.
- try! throwPythonErrorIfPresent()
- return PythonObject(consuming: result!)
+ _ op: PythonBinaryOp, lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ let result = op(lhs.borrowedPyObject, rhs.borrowedPyObject)
+ // If binary operation fails (e.g. due to `TypeError`), throw an exception.
+ try! throwPythonErrorIfPresent()
+ return PythonObject(consuming: result!)
+}
+
+private func performUnaryOp(
+ _ op: PythonUnaryOp, operand: PythonObject) -> PythonObject {
+ let result = op(operand.borrowedPyObject)
+ // If unary operation fails (e.g. due to `TypeError`), throw an exception.
+ try! throwPythonErrorIfPresent()
+ return PythonObject(consuming: result!)
}
public extension PythonObject {
- static func + (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
- return performBinaryOp(PyNumber_Add, lhs: lhs, rhs: rhs)
- }
+ static func + (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_Add, lhs: lhs, rhs: rhs)
+ }
- static func - (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
- return performBinaryOp(PyNumber_Subtract, lhs: lhs, rhs: rhs)
- }
+ static func - (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_Subtract, lhs: lhs, rhs: rhs)
+ }
+
+ static func * (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_Multiply, lhs: lhs, rhs: rhs)
+ }
+
+ static func / (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_TrueDivide, lhs: lhs, rhs: rhs)
+ }
- static func * (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
- return performBinaryOp(PyNumber_Multiply, lhs: lhs, rhs: rhs)
- }
+ static func += (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceAdd, lhs: lhs, rhs: rhs)
+ }
+
+ static func -= (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceSubtract, lhs: lhs, rhs: rhs)
+ }
+
+ static func *= (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceMultiply, lhs: lhs, rhs: rhs)
+ }
- static func / (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
- return performBinaryOp(PyNumber_TrueDivide, lhs: lhs, rhs: rhs)
- }
+ static func /= (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceTrueDivide, lhs: lhs, rhs: rhs)
+ }
+}
+
+public extension PythonObject {
+ static func & (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_And, lhs: lhs, rhs: rhs)
+ }
+
+ static func | (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_Or, lhs: lhs, rhs: rhs)
+ }
+
+ static func ^ (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return performBinaryOp(PyNumber_Xor, lhs: lhs, rhs: rhs)
+ }
- static func += (lhs: inout PythonObject, rhs: PythonObject) {
- lhs = performBinaryOp(PyNumber_InPlaceAdd, lhs: lhs, rhs: rhs)
- }
+ static func &= (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceAnd, lhs: lhs, rhs: rhs)
+ }
- static func -= (lhs: inout PythonObject, rhs: PythonObject) {
- lhs = performBinaryOp(PyNumber_InPlaceSubtract, lhs: lhs, rhs: rhs)
- }
+ static func |= (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceOr, lhs: lhs, rhs: rhs)
+ }
- static func *= (lhs: inout PythonObject, rhs: PythonObject) {
- lhs = performBinaryOp(PyNumber_InPlaceMultiply, lhs: lhs, rhs: rhs)
- }
+ static func ^= (lhs: inout PythonObject, rhs: PythonObject) {
+ lhs = performBinaryOp(PyNumber_InPlaceXor, lhs: lhs, rhs: rhs)
+ }
- static func /= (lhs: inout PythonObject, rhs: PythonObject) {
- lhs = performBinaryOp(PyNumber_InPlaceTrueDivide, lhs: lhs, rhs: rhs)
- }
+ static prefix func ~ (_ operand: Self) -> Self {
+ return performUnaryOp(PyNumber_Invert, operand: operand)
+ }
}
extension PythonObject : SignedNumeric {
- public init(exactly value: T) {
- self.init(Int(value))
- }
+ public init(exactly value: T) {
+ self.init(Int(value))
+ }
- public typealias Magnitude = PythonObject
+ public typealias Magnitude = PythonObject
- public var magnitude: PythonObject {
- return self < 0 ? -self : self
- }
+ public var magnitude: PythonObject {
+ return self < 0 ? -self : self
+ }
+
+ // Override the default implementation of `-` prefix function
+ // from SignedNumeric (https://bugs.swift.org/browse/SR-13293).
+ public static prefix func - (_ operand: Self) -> Self {
+ return performUnaryOp(PyNumber_Negative, operand: operand)
+ }
}
extension PythonObject : Strideable {
- public typealias Stride = PythonObject
+ public typealias Stride = PythonObject
- public func distance(to other: PythonObject) -> Stride {
- return other - self
- }
+ public func distance(to other: PythonObject) -> Stride {
+ return other - self
+ }
- public func advanced(by stride: Stride) -> PythonObject {
- return self + stride
- }
+ public func advanced(by stride: Stride) -> PythonObject {
+ return self + stride
+ }
}
extension PythonObject : Equatable, Comparable {
- // `Equatable` and `Comparable` are implemented using rich comparison.
- // This is consistent with how Python handles comparisons.
- private func compared(to other: PythonObject, byOp: Int32) -> Bool {
- let lhsObject = ownedPyObject
- let rhsObject = other.ownedPyObject
- defer {
- Py_DecRef(lhsObject)
- Py_DecRef(rhsObject)
- }
- assert(PyErr_Occurred() == nil,
- "Python error occurred somewhere but wasn't handled")
- switch PyObject_RichCompareBool(lhsObject, rhsObject, byOp) {
- case 0: return false
- case 1: return true
- default:
- try! throwPythonErrorIfPresent()
- fatalError(
- "No result or error returned when comparing \(self) to \(other)")
- }
- }
-
- public static func == (lhs: PythonObject, rhs: PythonObject) -> Bool {
- return lhs.compared(to: rhs, byOp: Py_EQ)
- }
-
- public static func != (lhs: PythonObject, rhs: PythonObject) -> Bool {
- return lhs.compared(to: rhs, byOp: Py_NE)
- }
-
- public static func < (lhs: PythonObject, rhs: PythonObject) -> Bool {
- return lhs.compared(to: rhs, byOp: Py_LT)
- }
-
- public static func <= (lhs: PythonObject, rhs: PythonObject) -> Bool {
- return lhs.compared(to: rhs, byOp: Py_LE)
- }
-
- public static func > (lhs: PythonObject, rhs: PythonObject) -> Bool {
- return lhs.compared(to: rhs, byOp: Py_GT)
- }
-
- public static func >= (lhs: PythonObject, rhs: PythonObject) -> Bool {
- return lhs.compared(to: rhs, byOp: Py_GE)
- }
+ // `Equatable` and `Comparable` are implemented using rich comparison.
+ // This is consistent with how Python handles comparisons.
+ private func compared(to other: PythonObject, byOp: Int32) -> Bool {
+ let lhsObject = ownedPyObject
+ let rhsObject = other.ownedPyObject
+ defer {
+ Py_DecRef(lhsObject)
+ Py_DecRef(rhsObject)
+ }
+ assert(PyErr_Occurred() == nil,
+ "Python error occurred somewhere but wasn't handled")
+ switch PyObject_RichCompareBool(lhsObject, rhsObject, byOp) {
+ case 0: return false
+ case 1: return true
+ default:
+ try! throwPythonErrorIfPresent()
+ fatalError("No result or error returned when comparing \(self) to \(other)")
+ }
+ }
+
+ public static func == (lhs: PythonObject, rhs: PythonObject) -> Bool {
+ return lhs.compared(to: rhs, byOp: Py_EQ)
+ }
+
+ public static func != (lhs: PythonObject, rhs: PythonObject) -> Bool {
+ return lhs.compared(to: rhs, byOp: Py_NE)
+ }
+
+ public static func < (lhs: PythonObject, rhs: PythonObject) -> Bool {
+ return lhs.compared(to: rhs, byOp: Py_LT)
+ }
+
+ public static func <= (lhs: PythonObject, rhs: PythonObject) -> Bool {
+ return lhs.compared(to: rhs, byOp: Py_LE)
+ }
+
+ public static func > (lhs: PythonObject, rhs: PythonObject) -> Bool {
+ return lhs.compared(to: rhs, byOp: Py_GT)
+ }
+
+ public static func >= (lhs: PythonObject, rhs: PythonObject) -> Bool {
+ return lhs.compared(to: rhs, byOp: Py_GE)
+ }
+}
+
+public extension PythonObject {
+ private func compared(to other: PythonObject, byOp: Int32) -> PythonObject {
+ let lhsObject = ownedPyObject
+ let rhsObject = other.ownedPyObject
+ defer {
+ Py_DecRef(lhsObject)
+ Py_DecRef(rhsObject)
+ }
+ assert(PyErr_Occurred() == nil,
+ "Python error occurred somewhere but wasn't handled")
+ guard let result = PyObject_RichCompare(lhsObject, rhsObject, byOp) else {
+ // If a Python exception was thrown, throw a corresponding Swift error.
+ try! throwPythonErrorIfPresent()
+ fatalError("No result or error returned when comparing \(self) to \(other)")
+ }
+ return PythonObject(consuming: result)
+ }
+
+ static func == (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return lhs.compared(to: rhs, byOp: Py_EQ)
+ }
+
+ static func != (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return lhs.compared(to: rhs, byOp: Py_NE)
+ }
+
+ static func < (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return lhs.compared(to: rhs, byOp: Py_LT)
+ }
+
+ static func <= (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return lhs.compared(to: rhs, byOp: Py_LE)
+ }
+
+ static func > (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return lhs.compared(to: rhs, byOp: Py_GT)
+ }
+
+ static func >= (lhs: PythonObject, rhs: PythonObject) -> PythonObject {
+ return lhs.compared(to: rhs, byOp: Py_GE)
+ }
}
extension PythonObject : Hashable {
- public func hash(into hasher: inout Hasher) {
- guard let hash = Int(self.__hash__()) else {
- fatalError("Cannot use '__hash__' on \(self)")
+ public func hash(into hasher: inout Hasher) {
+ guard let hash = Int(self.__hash__()) else {
+ fatalError("Cannot use '__hash__' on \(self)")
+ }
+ hasher.combine(hash)
}
- hasher.combine(hash)
- }
}
extension PythonObject : MutableCollection {
- public typealias Index = PythonObject
- public typealias Element = PythonObject
-
- public var startIndex: Index {
- return 0
- }
+ public typealias Index = PythonObject
+ public typealias Element = PythonObject
- public var endIndex: Index {
- return Python.len(self)
- }
+ public var startIndex: Index {
+ return 0
+ }
- public func index(after i: Index) -> Index {
- return i + PythonObject(1)
- }
+ public var endIndex: Index {
+ return Python.len(self)
+ }
- public subscript(index: PythonObject) -> PythonObject {
- get {
- return self[index as PythonConvertible]
+ public func index(after i: Index) -> Index {
+ return i + PythonObject(1)
}
- set {
- self[index as PythonConvertible] = newValue
+
+ public subscript(index: PythonObject) -> PythonObject {
+ get {
+ return self[index as PythonConvertible]
+ }
+ set {
+ self[index as PythonConvertible] = newValue
+ }
}
- }
}
extension PythonObject : Sequence {
- public struct Iterator : IteratorProtocol {
- fileprivate let pythonIterator: PythonObject
+ public struct Iterator : IteratorProtocol {
+ fileprivate let pythonIterator: PythonObject
+
+ public func next() -> PythonObject? {
+ guard let result = PyIter_Next(self.pythonIterator.borrowedPyObject) else {
+ try! throwPythonErrorIfPresent()
+ return nil
+ }
+ return PythonObject(consuming: result)
+ }
+ }
- public func next() -> PythonObject? {
- guard let result = PyIter_Next(self.pythonIterator.borrowedPyObject) else {
- try! throwPythonErrorIfPresent()
- return nil
- }
- return PythonObject(consuming: result)
+ public func makeIterator() -> Iterator {
+ guard let result = PyObject_GetIter(borrowedPyObject) else {
+ try! throwPythonErrorIfPresent()
+ // Unreachable. A Python `TypeError` must have been thrown.
+ preconditionFailure()
+ }
+ return Iterator(pythonIterator: PythonObject(consuming: result))
}
- }
+}
- public func makeIterator() -> Iterator {
- guard let result = PyObject_GetIter(borrowedPyObject) else {
- try! throwPythonErrorIfPresent()
- // Unreachable. A Python `TypeError` must have been thrown.
- preconditionFailure()
+extension PythonObject {
+ public var count: Int {
+ checking.count!
}
- return Iterator(pythonIterator: PythonObject(consuming: result))
- }
}
//===----------------------------------------------------------------------===//
// `ExpressibleByLiteral` conformances
//===----------------------------------------------------------------------===//
-extension PythonObject : ExpressibleByBooleanLiteral,
- ExpressibleByIntegerLiteral,
- ExpressibleByFloatLiteral,
- ExpressibleByStringLiteral {
- public init(booleanLiteral value: Bool) {
- self.init(value)
- }
- public init(integerLiteral value: Int) {
- self.init(value)
- }
- public init(floatLiteral value: Double) {
- self.init(value)
- }
- public init(stringLiteral value: String) {
- self.init(value)
- }
-}
-
-extension PythonObject : ExpressibleByArrayLiteral,
- ExpressibleByDictionaryLiteral {
- public init(arrayLiteral elements: PythonObject...) {
- self.init(elements)
- }
- public typealias Key = PythonObject
- public typealias Value = PythonObject
- public init(dictionaryLiteral elements: (PythonObject, PythonObject)...) {
- self.init(Dictionary(elements, uniquingKeysWith: { lhs, _ in lhs }))
- }
+extension PythonObject : ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral,
+ExpressibleByFloatLiteral, ExpressibleByStringLiteral {
+ public init(booleanLiteral value: Bool) {
+ self.init(value)
+ }
+ public init(integerLiteral value: Int) {
+ self.init(value)
+ }
+ public init(floatLiteral value: Double) {
+ self.init(value)
+ }
+ public init(stringLiteral value: String) {
+ self.init(value)
+ }
+}
+
+extension PythonObject : ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {
+ public init(arrayLiteral elements: PythonObject...) {
+ self.init(elements)
+ }
+ public typealias Key = PythonObject
+ public typealias Value = PythonObject
+
+ // Preserves element order in the final Python object, unlike
+ // `Dictionary.pythonObject`. When keys are duplicated, throw the same
+ // runtime error as `Swift.Dictionary.init(dictionaryLiteral:)`. This
+ // differs from Python's key uniquing semantics, which silently override an
+ // existing key with the next one it encounters.
+ public init(dictionaryLiteral elements: (PythonObject, PythonObject)...) {
+ _ = Python // Ensure Python is initialized.
+ let dict = PyDict_New()!
+ for (key, value) in elements {
+ let k = key.ownedPyObject
+ let v = value.ownedPyObject
+
+ // Use Python's native key checking instead of querying whether
+ // `elements` contains the key. Although this could theoretically
+ // produce different results, it produces the Python object we want.
+ switch PyDict_Contains(dict, k) {
+ case 0:
+ PyDict_SetItem(dict, k, v)
+ case 1:
+ fatalError("Dictionary literal contains duplicate keys")
+ default:
+ try! throwPythonErrorIfPresent()
+ fatalError("No result or error checking whether \(elements) contains \(key)")
+ }
+
+ Py_DecRef(k)
+ Py_DecRef(v)
+ }
+ self.init(consuming: dict)
+ }
+}
+
+public struct PythonBytes : PythonConvertible, ConvertibleFromPython, Hashable {
+ public private(set) var pythonObject: PythonObject
+
+ public init?(_ pythonObject: PythonObject) {
+ // We try to get the string/size pointers out. If it works, hooray, this is a bytes
+ // otherwise it isn't.
+ let pyObject = pythonObject.ownedPyObject
+ defer { Py_DecRef(pyObject) }
+
+ var length = 0
+ var buffer: UnsafeMutablePointer? = nil
+
+ switch PyBytes_AsStringAndSize(pyObject, &buffer, &length) {
+ case 0:
+ self.pythonObject = pythonObject
+ default:
+ return nil
+ }
+ }
+
+ @inlinable
+ public init(_ bytes: Bytes) where Bytes.Element == UInt8 {
+ let possibleSelf = bytes.withContiguousStorageIfAvailable { storagePtr in
+ PythonBytes.fromBytePointer(storagePtr)
+ }
+ if let actualSelf = possibleSelf {
+ self = actualSelf
+ } else {
+ let temporaryBuffer = Array(bytes)
+ self = temporaryBuffer.withUnsafeBufferPointer {
+ PythonBytes.fromBytePointer($0)
+ }
+ }
+ }
+
+ @inlinable
+ public init(_ bytes: Bytes) where Bytes.Element == Int8 {
+ let possibleSelf = bytes.withContiguousStorageIfAvailable { storagePtr in
+ PythonBytes.fromBytePointer(storagePtr)
+ }
+ if let actualSelf = possibleSelf {
+ self = actualSelf
+ } else {
+ let temporaryBuffer = Array(bytes)
+ self = temporaryBuffer.withUnsafeBufferPointer {
+ PythonBytes.fromBytePointer($0)
+ }
+ }
+ }
+
+ private init(bytesObject: PythonObject) {
+ self.pythonObject = bytesObject
+ }
+
+ @usableFromInline
+ static func fromBytePointer(_ bytes: UnsafeBufferPointer) -> PythonBytes {
+ bytes.withMemoryRebound(to: Int8.self) { reboundPtr in
+ PythonBytes.fromBytePointer(reboundPtr)
+ }
+ }
+
+ @usableFromInline
+ static func fromBytePointer(_ bytes: UnsafeBufferPointer) -> PythonBytes {
+ let v = PyBytes_FromStringAndSize(bytes.baseAddress, bytes.count)!
+ return PythonBytes(bytesObject: PythonObject(consuming: v))
+ }
+
+ public func withUnsafeBytes(
+ _ callback: (UnsafeRawBufferPointer) throws -> ReturnValue
+ ) rethrows -> ReturnValue {
+ let pyObject = self.pythonObject.ownedPyObject
+ defer { Py_DecRef(pyObject) }
+
+ var length = 0
+ var buffer: UnsafeMutablePointer? = nil
+
+ switch PyBytes_AsStringAndSize(pyObject, &buffer, &length) {
+ case 0:
+ let buffer = UnsafeRawBufferPointer(start: buffer, count: length)
+ return try callback(buffer)
+ default:
+ try! throwPythonErrorIfPresent()
+ fatalError("No result or error getting interior buffer for bytes \(self)")
+ }
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// PythonFunction - create functions in Swift that can be called from Python
+//===----------------------------------------------------------------------===//
+
+/// Create functions in Swift that can be called from Python
+///
+/// Example:
+///
+/// The Python code `map(lambda(x: x * 2), [10, 12, 14])` would be written as:
+///
+/// Python.map(PythonFunction { x in x * 2 }, [10, 12, 14]) // [20, 24, 28]
+///
+final class PyFunction {
+ enum CallingConvention {
+ case varArgs
+ case varArgsWithKeywords
+ }
+
+ /// Allows `PyFunction` to store Python functions with more than one possible calling convention
+ var callingConvention: CallingConvention
+
+ /// `arguments` is a Python tuple.
+ typealias VarArgsFunction = (
+ _ arguments: PythonObject) throws -> PythonConvertible
+
+ /// `arguments` is a Python tuple.
+ /// `keywordArguments` is an OrderedDict in Python 3.6 and later, or a dict otherwise.
+ typealias VarArgsWithKeywordsFunction = (
+ _ arguments: PythonObject,
+ _ keywordArguments: PythonObject) throws -> PythonConvertible
+
+ /// Has the same memory layout as any other function with the Swift calling convention
+ private typealias Storage = () throws -> PythonConvertible
+
+ /// Stores all function pointers in the same stored property. `callAsFunction` casts this into the desired type.
+ private var callSwiftFunction: Storage
+
+ init(_ callSwiftFunction: @escaping VarArgsFunction) {
+ self.callingConvention = .varArgs
+ self.callSwiftFunction = unsafeBitCast(callSwiftFunction, to: Storage.self)
+ }
+
+ init(_ callSwiftFunction: @escaping VarArgsWithKeywordsFunction) {
+ self.callingConvention = .varArgsWithKeywords
+ self.callSwiftFunction = unsafeBitCast(callSwiftFunction, to: Storage.self)
+ }
+
+ private func checkConvention(_ calledConvention: CallingConvention) {
+ precondition(callingConvention == calledConvention,
+ "Called PyFunction with convention \(calledConvention), but expected \(callingConvention)")
+ }
+
+ func callAsFunction(_ argumentsTuple: PythonObject) throws -> PythonConvertible {
+ checkConvention(.varArgs)
+ let callSwiftFunction = unsafeBitCast(self.callSwiftFunction, to: VarArgsFunction.self)
+ return try callSwiftFunction(argumentsTuple)
+ }
+
+ func callAsFunction(_ argumentsTuple: PythonObject, _ keywordArguments: PythonObject) throws -> PythonConvertible {
+ checkConvention(.varArgsWithKeywords)
+ let callSwiftFunction = unsafeBitCast(self.callSwiftFunction, to: VarArgsWithKeywordsFunction.self)
+ return try callSwiftFunction(argumentsTuple, keywordArguments)
+ }
+}
+
+public struct PythonFunction {
+ /// Called directly by the Python C API
+ private var function: PyFunction
+
+ @_disfavoredOverload
+ public init(_ fn: @escaping (PythonObject) throws -> PythonConvertible) {
+ function = PyFunction { argumentsAsTuple in
+ return try fn(argumentsAsTuple[0])
+ }
+ }
+
+ /// For cases where the Swift function should accept more (or less) than one parameter, accept an ordered array of all arguments instead.
+ public init(_ fn: @escaping ([PythonObject]) throws -> PythonConvertible) {
+ function = PyFunction { argumentsAsTuple in
+ return try fn(argumentsAsTuple.map { $0 })
+ }
+ }
+
+ /// For cases where the Swift function should accept keyword arguments as `**kwargs` in Python.
+ /// `**kwargs` must preserve order from Python 3.6 onward, similarly to
+ /// Swift `KeyValuePairs` and unlike `Dictionary`. `KeyValuePairs` cannot be
+ /// mutated, so the next best solution is to use `[KeyValuePairs.Element]`.
+ public init(_ fn: @escaping ([PythonObject], [(key: String, value: PythonObject)]) throws -> PythonConvertible) {
+ function = PyFunction { argumentsAsTuple, keywordArgumentsAsDictionary in
+ var kwargs: [(String, PythonObject)] = []
+ for keyAndValue in keywordArgumentsAsDictionary.items() {
+ let (key, value) = keyAndValue.tuple2
+ kwargs.append((String(key)!, value))
+ }
+ return try fn(argumentsAsTuple.map { $0 }, kwargs)
+ }
+ }
+}
+
+extension PythonFunction : PythonConvertible {
+ public var pythonObject: PythonObject {
+ // Ensure Python is initialized, and check for version match.
+ let versionMajor = Python.versionInfo.major
+ let versionMinor = Python.versionInfo.minor
+ guard (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3 else {
+ fatalError("PythonFunction only supports Python 3.1 and above.")
+ }
+
+ let destructor: @convention(c) (PyObjectPointer?) -> Void = { capsulePointer in
+ let funcPointer = PyCapsule_GetPointer(capsulePointer, nil)
+ Unmanaged.fromOpaque(funcPointer).release()
+ }
+ let funcPointer = Unmanaged.passRetained(function).toOpaque()
+ let capsulePointer = PyCapsule_New(
+ funcPointer,
+ nil,
+ unsafeBitCast(destructor, to: OpaquePointer.self)
+ )
+
+ var methodDefinition: UnsafeMutablePointer
+ switch function.callingConvention {
+ case .varArgs:
+ methodDefinition = PythonFunction.sharedMethodDefinition
+ case .varArgsWithKeywords:
+ methodDefinition = PythonFunction.sharedMethodWithKeywordsDefinition
+ }
+ let pyFuncPointer = PyCFunction_NewEx(
+ methodDefinition,
+ capsulePointer,
+ nil
+ )
+
+ return PythonObject(consuming: pyFuncPointer)
+ }
+}
+
+fileprivate extension PythonFunction {
+ static let sharedMethodDefinition: UnsafeMutablePointer = {
+ let name: StaticString = "pythonkit_swift_function"
+ // `utf8Start` is a property of StaticString, thus, it has a stable pointer.
+ let namePointer = UnsafeRawPointer(name.utf8Start).assumingMemoryBound(to: Int8.self)
+
+ let methodImplementationPointer = unsafeBitCast(
+ PythonFunction.sharedMethodImplementation, to: OpaquePointer.self)
+
+ /// The standard calling convention. See Python C API docs
+ let METH_VARARGS = 0x0001 as Int32
+
+ let pointer = UnsafeMutablePointer.allocate(capacity: 1)
+ pointer.pointee = PyMethodDef(
+ ml_name: namePointer,
+ ml_meth: methodImplementationPointer,
+ ml_flags: METH_VARARGS,
+ ml_doc: nil
+ )
+
+ return pointer
+ }()
+
+ static let sharedMethodWithKeywordsDefinition: UnsafeMutablePointer = {
+ let name: StaticString = "pythonkit_swift_function_with_keywords"
+ // `utf8Start` is a property of StaticString, thus, it has a stable pointer.
+ let namePointer = UnsafeRawPointer(name.utf8Start).assumingMemoryBound(to: Int8.self)
+
+ let methodImplementationPointer = unsafeBitCast(
+ PythonFunction.sharedMethodWithKeywordsImplementation, to: OpaquePointer.self)
+
+ /// A combination of flags that supports `**kwargs`. See Python C API docs
+ let METH_VARARGS = 0x0001 as Int32
+ let METH_KEYWORDS = 0x0002 as Int32
+
+ let pointer = UnsafeMutablePointer.allocate(capacity: 1)
+ pointer.pointee = PyMethodDef(
+ ml_name: namePointer,
+ ml_meth: methodImplementationPointer,
+ ml_flags: METH_VARARGS | METH_KEYWORDS,
+ ml_doc: nil
+ )
+
+ return pointer
+ }()
+
+ private static let sharedMethodImplementation: @convention(c) (
+ PyObjectPointer?, PyObjectPointer?
+ ) -> PyObjectPointer? = { context, argumentsPointer in
+ guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
+ return nil
+ }
+
+ let funcPointer = PyCapsule_GetPointer(capsulePointer, nil)
+ let function = Unmanaged.fromOpaque(funcPointer).takeUnretainedValue()
+
+ do {
+ let argumentsAsTuple = PythonObject(consuming: argumentsPointer)
+ return try function(argumentsAsTuple).ownedPyObject
+ } catch {
+ PythonFunction.setPythonError(swiftError: error)
+ return nil // This must only be `nil` if an exception has been set
+ }
+ }
+
+ private static let sharedMethodWithKeywordsImplementation: @convention(c) (
+ PyObjectPointer?, PyObjectPointer?, PyObjectPointer?
+ ) -> PyObjectPointer? = { context, argumentsPointer, keywordArgumentsPointer in
+ guard let argumentsPointer = argumentsPointer, let capsulePointer = context else {
+ return nil
+ }
+
+ let funcPointer = PyCapsule_GetPointer(capsulePointer, nil)
+ let function = Unmanaged.fromOpaque(funcPointer).takeUnretainedValue()
+
+ do {
+ let argumentsAsTuple = PythonObject(consuming: argumentsPointer)
+ var keywordArgumentsAsDictionary: PythonObject
+ if let keywordArgumentsPointer = keywordArgumentsPointer {
+ keywordArgumentsAsDictionary = PythonObject(consuming: keywordArgumentsPointer)
+ } else {
+ keywordArgumentsAsDictionary = [:]
+ }
+ return try function(argumentsAsTuple, keywordArgumentsAsDictionary).ownedPyObject
+ } catch {
+ PythonFunction.setPythonError(swiftError: error)
+ return nil // This must only be `nil` if an exception has been set
+ }
+ }
+
+ private static func setPythonError(swiftError: Error) {
+ if let pythonObject = swiftError as? PythonObject {
+ if Bool(Python.isinstance(pythonObject, Python.BaseException))! {
+ // We are an instance of an Exception class type. Set the exception class to the object's type:
+ PyErr_SetString(Python.type(pythonObject).ownedPyObject, pythonObject.description)
+ } else {
+ // Assume an actual class type was thrown (rather than an instance)
+ // Crashes if it was neither a subclass of BaseException nor an instance of one.
+ //
+ // We *could* check to see whether `pythonObject` is a class here and fall back
+ // to the default case of setting a generic Exception, below, but we also want
+ // people to write valid code.
+ PyErr_SetString(pythonObject.ownedPyObject, pythonObject.description)
+ }
+ } else {
+ // Make a generic Python Exception based on the Swift Error:
+ PyErr_SetString(Python.Exception.ownedPyObject, "\(type(of: swiftError)) raised in Swift: \(swiftError)")
+ }
+ }
+}
+
+extension PythonObject: Error {}
+
+// From Python's C Headers:
+struct PyMethodDef {
+ /// The name of the built-in function/method
+ var ml_name: UnsafePointer
+
+ /// The C function that implements it.
+ /// Since this accepts multiple function signatures, the Swift type must be opaque here.
+ var ml_meth: OpaquePointer
+
+ /// Combination of METH_xxx flags, which mostly describe the args expected by the C func
+ var ml_flags: Int32
+
+ /// The __doc__ attribute, or NULL
+ var ml_doc: UnsafePointer?
+}
+
+//===----------------------------------------------------------------------===//
+// PythonInstanceMethod - create functions that can be bound to a Python object
+//===----------------------------------------------------------------------===//
+
+public struct PythonInstanceMethod {
+ private var function: PythonFunction
+
+ @_disfavoredOverload
+ public init(_ fn: @escaping (PythonObject) throws -> PythonConvertible) {
+ function = PythonFunction(fn)
+ }
+
+ public init(_ fn: @escaping ([PythonObject]) throws -> PythonConvertible) {
+ function = PythonFunction(fn)
+ }
+
+ public init(_ fn: @escaping ([PythonObject], [(key: String, value: PythonObject)]) throws -> PythonConvertible) {
+ function = PythonFunction(fn)
+ }
+}
+
+extension PythonInstanceMethod : PythonConvertible {
+ public var pythonObject: PythonObject {
+ let pyFuncPointer = function.pythonObject.ownedPyObject
+ let methodPointer = PyInstanceMethod_New(pyFuncPointer)
+ return PythonObject(consuming: methodPointer)
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// PythonClass - construct subclasses of a Python class
+//===----------------------------------------------------------------------===//
+
+public struct PythonClass {
+ private var typeObject: PythonObject
+
+ public struct Members: ExpressibleByDictionaryLiteral {
+ public typealias Key = String
+ public typealias Value = PythonConvertible
+
+ var dictionary: [String: PythonObject]
+
+ public init(dictionaryLiteral elements: (Key, Value)...) {
+ let castedElements = elements.map { (key, value) in
+ (key, value.pythonObject)
+ }
+
+ dictionary = Dictionary(castedElements, uniquingKeysWith: { _, _ in
+ fatalError("Dictionary literal contains duplicate keys")
+ })
+ }
+ }
+
+ public init(_ name: String, superclasses: [PythonObject] = [], members: Members = [:]) {
+ self.init(name, superclasses: superclasses, members: members.dictionary)
+ }
+
+ @_disfavoredOverload
+ public init(_ name: String, superclasses: [PythonObject] = [], members: [String: PythonObject] = [:]) {
+ var trueSuperclasses = superclasses
+ if !trueSuperclasses.contains(Python.object) {
+ trueSuperclasses.append(Python.object)
+ }
+
+ let superclassesTuple = PythonObject(tupleContentsOf: trueSuperclasses)
+ typeObject = Python.type(name, superclassesTuple, members.pythonObject)
+ }
+}
+
+extension PythonClass : PythonConvertible {
+ public var pythonObject: PythonObject {
+ typeObject
+ }
}
diff --git a/PythonKit/PythonLibrary+Symbols.swift b/PythonKit/PythonLibrary+Symbols.swift
index d3dab7c..c4db681 100644
--- a/PythonKit/PythonLibrary+Symbols.swift
+++ b/PythonKit/PythonLibrary+Symbols.swift
@@ -15,13 +15,17 @@
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
-// Required Python typealias and constants.
+// Required Python typealiases and constants.
//===----------------------------------------------------------------------===//
+@usableFromInline
typealias PyObjectPointer = UnsafeMutableRawPointer
+typealias PyMethodDefPointer = UnsafeMutableRawPointer
typealias PyCCharPointer = UnsafePointer
typealias PyBinaryOperation =
- @convention(c) (PyObjectPointer?, PyObjectPointer?) -> PyObjectPointer?
+ @convention(c) (PyObjectPointer?, PyObjectPointer?) -> PyObjectPointer?
+typealias PyUnaryOperation =
+ @convention(c) (PyObjectPointer?) -> PyObjectPointer?
let Py_LT: Int32 = 0
let Py_LE: Int32 = 1
@@ -35,181 +39,248 @@ let Py_GE: Int32 = 5
//===----------------------------------------------------------------------===//
let Py_Initialize: @convention(c) () -> Void =
- PythonLibrary.loadSymbol(name: "Py_Initialize")
+ PythonLibrary.loadSymbol(name: "Py_Initialize")
let Py_IncRef: @convention(c) (PyObjectPointer?) -> Void =
- PythonLibrary.loadSymbol(name: "Py_IncRef")
+ PythonLibrary.loadSymbol(name: "Py_IncRef")
let Py_DecRef: @convention(c) (PyObjectPointer?) -> Void =
- PythonLibrary.loadSymbol(name: "Py_DecRef")
+ PythonLibrary.loadSymbol(name: "Py_DecRef")
let PyImport_ImportModule: @convention(c) (
- PyCCharPointer) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyImport_ImportModule")
+ PyCCharPointer) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyImport_ImportModule")
let PyEval_GetBuiltins: @convention(c) () -> PyObjectPointer =
- PythonLibrary.loadSymbol(name: "PyEval_GetBuiltins")
+ PythonLibrary.loadSymbol(name: "PyEval_GetBuiltins")
let PyRun_SimpleString: @convention(c) (PyCCharPointer) -> Void =
PythonLibrary.loadSymbol(name: "PyRun_SimpleString")
+let PyCFunction_NewEx: @convention(c) (PyMethodDefPointer, UnsafeMutableRawPointer, UnsafeMutableRawPointer?) -> PyObjectPointer =
+ PythonLibrary.loadSymbol(name: "PyCFunction_NewEx")
+
+let PyInstanceMethod_New: @convention(c) (PyObjectPointer) -> PyObjectPointer =
+ PythonLibrary.loadSymbol(name: "PyInstanceMethod_New")
+
+/// The last argument would ideally be of type `@convention(c) (PyObjectPointer?) -> Void`.
+/// Due to SR-15871 and the source-breaking nature of potential workarounds, the
+/// static typing was removed. The caller must now manually cast a closure to
+/// `OpaquePointer` before passing it into `PyCapsule_New`.
+let PyCapsule_New: @convention(c) (
+ UnsafeMutableRawPointer, UnsafePointer?,
+ OpaquePointer) -> PyObjectPointer =
+ PythonLibrary.loadSymbol(name: "PyCapsule_New")
+
+let PyCapsule_GetPointer: @convention(c) (PyObjectPointer?, UnsafePointer?) -> UnsafeMutableRawPointer =
+ PythonLibrary.loadSymbol(name: "PyCapsule_GetPointer")
+
+let PyErr_SetString: @convention(c) (PyObjectPointer, UnsafePointer?) -> Void =
+ PythonLibrary.loadSymbol(name: "PyErr_SetString")
+
let PyErr_Occurred: @convention(c) () -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyErr_Occurred")
+ PythonLibrary.loadSymbol(name: "PyErr_Occurred")
let PyErr_Clear: @convention(c) () -> Void =
- PythonLibrary.loadSymbol(name: "PyErr_Clear")
+ PythonLibrary.loadSymbol(name: "PyErr_Clear")
let PyErr_Fetch: @convention(c) (
- UnsafeMutablePointer,
- UnsafeMutablePointer,
- UnsafeMutablePointer) -> Void =
- PythonLibrary.loadSymbol(name: "PyErr_Fetch")
+ UnsafeMutablePointer,
+ UnsafeMutablePointer,
+ UnsafeMutablePointer) -> Void =
+ PythonLibrary.loadSymbol(name: "PyErr_Fetch")
let PyDict_New: @convention(c) () -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyDict_New")
+ PythonLibrary.loadSymbol(name: "PyDict_New")
+
+let PyDict_Contains: @convention(c) (
+ PyObjectPointer?, PyObjectPointer?) -> Int32 =
+ PythonLibrary.loadSymbol(name: "PyDict_Contains")
let PyDict_SetItem: @convention(c) (
- PyObjectPointer?, PyObjectPointer, PyObjectPointer) -> Void =
- PythonLibrary.loadSymbol(name: "PyDict_SetItem")
+ PyObjectPointer?, PyObjectPointer, PyObjectPointer) -> Void =
+ PythonLibrary.loadSymbol(name: "PyDict_SetItem")
let PyObject_GetItem: @convention(c) (
- PyObjectPointer, PyObjectPointer) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyObject_GetItem")
+ PyObjectPointer, PyObjectPointer) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyObject_GetItem")
let PyObject_SetItem: @convention(c) (
- PyObjectPointer, PyObjectPointer, PyObjectPointer) -> Void =
- PythonLibrary.loadSymbol(name: "PyObject_SetItem")
+ PyObjectPointer, PyObjectPointer, PyObjectPointer) -> Void =
+ PythonLibrary.loadSymbol(name: "PyObject_SetItem")
let PyObject_DelItem: @convention(c) (
- PyObjectPointer, PyObjectPointer) -> Void =
- PythonLibrary.loadSymbol(name: "PyObject_DelItem")
+ PyObjectPointer, PyObjectPointer) -> Void =
+ PythonLibrary.loadSymbol(name: "PyObject_DelItem")
let PyObject_Call: @convention(c) (
- PyObjectPointer, PyObjectPointer,
- PyObjectPointer?) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyObject_Call")
+ PyObjectPointer, PyObjectPointer,
+ PyObjectPointer?) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyObject_Call")
let PyObject_CallObject: @convention(c) (
- PyObjectPointer, PyObjectPointer) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyObject_CallObject")
+ PyObjectPointer, PyObjectPointer) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyObject_CallObject")
let PyObject_GetAttrString: @convention(c) (
- PyObjectPointer, PyCCharPointer) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyObject_GetAttrString")
+ PyObjectPointer, PyCCharPointer) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyObject_GetAttrString")
let PyObject_SetAttrString: @convention(c) (
- PyObjectPointer, PyCCharPointer, PyObjectPointer) -> Int32 =
- PythonLibrary.loadSymbol(name: "PyObject_SetAttrString")
+ PyObjectPointer, PyCCharPointer, PyObjectPointer) -> Int32 =
+ PythonLibrary.loadSymbol(name: "PyObject_SetAttrString")
let PySlice_New: @convention(c) (
- PyObjectPointer?, PyObjectPointer?,
- PyObjectPointer?) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PySlice_New")
+ PyObjectPointer?, PyObjectPointer?,
+ PyObjectPointer?) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PySlice_New")
let PyTuple_New: @convention(c) (Int) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyTuple_New")
+ PythonLibrary.loadSymbol(name: "PyTuple_New")
let PyTuple_SetItem: @convention(c) (
- PyObjectPointer, Int, PyObjectPointer) -> Void =
- PythonLibrary.loadSymbol(name: "PyTuple_SetItem")
+ PyObjectPointer, Int, PyObjectPointer) -> Void =
+ PythonLibrary.loadSymbol(name: "PyTuple_SetItem")
+
+let PyObject_RichCompare: @convention(c) (
+ PyObjectPointer, PyObjectPointer, Int32) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyObject_RichCompare")
let PyObject_RichCompareBool: @convention(c) (
- PyObjectPointer, PyObjectPointer, Int32) -> Int32 =
- PythonLibrary.loadSymbol(name: "PyObject_RichCompareBool")
+ PyObjectPointer, PyObjectPointer, Int32) -> Int32 =
+ PythonLibrary.loadSymbol(name: "PyObject_RichCompareBool")
let PyDict_Next: @convention(c) (
- PyObjectPointer, UnsafeMutablePointer,
- UnsafeMutablePointer,
- UnsafeMutablePointer) -> Int32 =
- PythonLibrary.loadSymbol(name: "PyDict_Next")
+ PyObjectPointer, UnsafeMutablePointer,
+ UnsafeMutablePointer,
+ UnsafeMutablePointer) -> Int32 =
+ PythonLibrary.loadSymbol(name: "PyDict_Next")
let PyIter_Next: @convention(c) (
- PyObjectPointer) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyIter_Next")
+ PyObjectPointer) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyIter_Next")
let PyObject_GetIter: @convention(c) (
- PyObjectPointer) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyObject_GetIter")
+ PyObjectPointer) -> PyObjectPointer? =
+ PythonLibrary.loadSymbol(name: "PyObject_GetIter")
let PyList_New: @convention(c) (Int) -> PyObjectPointer? =
- PythonLibrary.loadSymbol(name: "PyList_New")
+ PythonLibrary.loadSymbol(name: "PyList_New")
let PyList_SetItem: @convention(c) (
- PyObjectPointer, Int, PyObjectPointer) -> Int32 =
- PythonLibrary.loadSymbol(name: "PyList_SetItem")
+ PyObjectPointer, Int, PyObjectPointer) -> Int32 =
+ PythonLibrary.loadSymbol(name: "PyList_SetItem")
let PyBool_FromLong: @convention(c) (Int) -> PyObjectPointer =
- PythonLibrary.loadSymbol(name: "PyBool_FromLong")
+ PythonLibrary.loadSymbol(name: "PyBool_FromLong")
let PyFloat_AsDouble: @convention(c) (PyObjectPointer) -> Double =
- PythonLibrary.loadSymbol(name: "PyFloat_AsDouble")
+ PythonLibrary.loadSymbol(name: "PyFloat_AsDouble")
let PyFloat_FromDouble: @convention(c) (Double) -> PyObjectPointer =
- PythonLibrary.loadSymbol(name: "PyFloat_FromDouble")
+ PythonLibrary.loadSymbol(name: "PyFloat_FromDouble")
let PyInt_AsLong: @convention(c) (PyObjectPointer) -> Int =
- PythonLibrary.loadSymbol(
- name: "PyLong_AsLong",
- legacyName: "PyInt_AsLong")
+ PythonLibrary.loadSymbol(
+ name: "PyLong_AsLong",
+ legacyName: "PyInt_AsLong")
let PyInt_FromLong: @convention(c) (Int) -> PyObjectPointer =
- PythonLibrary.loadSymbol(
- name: "PyLong_FromLong",
- legacyName: "PyInt_FromLong")
+ PythonLibrary.loadSymbol(
+ name: "PyLong_FromLong",
+ legacyName: "PyInt_FromLong")
let PyInt_AsUnsignedLongMask: @convention(c) (PyObjectPointer) -> UInt =
- PythonLibrary.loadSymbol(
- name: "PyLong_AsUnsignedLongMask",
- legacyName: "PyInt_AsUnsignedLongMask")
+ PythonLibrary.loadSymbol(
+ name: "PyLong_AsUnsignedLongMask",
+ legacyName: "PyInt_AsUnsignedLongMask")
let PyInt_FromSize_t: @convention(c) (UInt) -> PyObjectPointer =
- PythonLibrary.loadSymbol(
- name: "PyLong_FromUnsignedLong",
- legacyName: "PyInt_FromSize_t")
+ PythonLibrary.loadSymbol(
+ name: "PyLong_FromUnsignedLong",
+ legacyName: "PyInt_FromSize_t")
let PyString_AsString: @convention(c) (PyObjectPointer) -> PyCCharPointer? =
- PythonLibrary.loadSymbol(
- name: "PyUnicode_AsUTF8",
- legacyName: "PyString_AsString")
+ PythonLibrary.loadSymbol(
+ name: "PyUnicode_AsUTF8",
+ legacyName: "PyString_AsString")
let PyString_FromStringAndSize: @convention(c) (
- PyCCharPointer?, Int) -> (PyObjectPointer?) =
- PythonLibrary.loadSymbol(
- name: "PyUnicode_DecodeUTF8",
- legacyName: "PyString_FromStringAndSize")
+ PyCCharPointer?, Int) -> (PyObjectPointer?) =
+ PythonLibrary.loadSymbol(
+ name: "PyUnicode_DecodeUTF8",
+ legacyName: "PyString_FromStringAndSize")
+
+let PyBytes_FromStringAndSize: @convention(c) (
+ PyCCharPointer?, Int) -> (PyObjectPointer?) =
+ PythonLibrary.loadSymbol(
+ name: "PyBytes_FromStringAndSize",
+ legacyName: "PyString_FromStringAndSize")
+
+let PyBytes_AsStringAndSize: @convention(c) (
+ PyObjectPointer,
+ UnsafeMutablePointer?>?,
+ UnsafeMutablePointer?) -> CInt =
+ PythonLibrary.loadSymbol(
+ name: "PyBytes_AsStringAndSize",
+ legacyName: "PyString_AsStringAndSize")
let _Py_ZeroStruct: PyObjectPointer =
- PythonLibrary.loadSymbol(name: "_Py_ZeroStruct")
+ PythonLibrary.loadSymbol(name: "_Py_ZeroStruct")
let _Py_TrueStruct: PyObjectPointer =
- PythonLibrary.loadSymbol(name: "_Py_TrueStruct")
+ PythonLibrary.loadSymbol(name: "_Py_TrueStruct")
let PyBool_Type: PyObjectPointer =
- PythonLibrary.loadSymbol(name: "PyBool_Type")
+ PythonLibrary.loadSymbol(name: "PyBool_Type")
let PySlice_Type: PyObjectPointer =
- PythonLibrary.loadSymbol(name: "PySlice_Type")
+ PythonLibrary.loadSymbol(name: "PySlice_Type")
let PyNumber_Add: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_Add")
+ PythonLibrary.loadSymbol(name: "PyNumber_Add")
let PyNumber_Subtract: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_Subtract")
+ PythonLibrary.loadSymbol(name: "PyNumber_Subtract")
let PyNumber_Multiply: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_Multiply")
+ PythonLibrary.loadSymbol(name: "PyNumber_Multiply")
let PyNumber_TrueDivide: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_TrueDivide")
+ PythonLibrary.loadSymbol(name: "PyNumber_TrueDivide")
let PyNumber_InPlaceAdd: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_InPlaceAdd")
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceAdd")
let PyNumber_InPlaceSubtract: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_InPlaceSubtract")
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceSubtract")
let PyNumber_InPlaceMultiply: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_InPlaceMultiply")
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceMultiply")
let PyNumber_InPlaceTrueDivide: PyBinaryOperation =
- PythonLibrary.loadSymbol(name: "PyNumber_InPlaceTrueDivide")
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceTrueDivide")
+
+let PyNumber_Negative: PyUnaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_Negative")
+
+let PyNumber_And: PyBinaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_And")
+
+let PyNumber_Or: PyBinaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_Or")
+
+let PyNumber_Xor: PyBinaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_Xor")
+
+let PyNumber_InPlaceAnd: PyBinaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceAnd")
+
+let PyNumber_InPlaceOr: PyBinaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceOr")
+
+let PyNumber_InPlaceXor: PyBinaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_InPlaceXor")
+
+let PyNumber_Invert: PyUnaryOperation =
+ PythonLibrary.loadSymbol(name: "PyNumber_Invert")
diff --git a/PythonKit/PythonLibrary.swift b/PythonKit/PythonLibrary.swift
index 6f81b9c..ab94a3f 100644
--- a/PythonKit/PythonLibrary.swift
+++ b/PythonKit/PythonLibrary.swift
@@ -18,8 +18,10 @@
import Darwin
#elseif canImport(Glibc)
import Glibc
+#elseif canImport(Musl)
+import Musl
#elseif os(Windows)
-import MSVCRT
+import CRT
import WinSDK
#endif
@@ -28,226 +30,287 @@ import WinSDK
//===----------------------------------------------------------------------===//
public struct PythonLibrary {
- private static let shared = PythonLibrary()
- private static let pythonLegacySymbolName = "PyString_AsString"
- private static var librarySymbolsLoaded = false
-
- private let pythonLibraryHandle: UnsafeMutableRawPointer
- private let isLegacyPython: Bool
-
- private init() {
- guard let pythonLibraryHandle = PythonLibrary.loadPythonLibrary() else {
- fatalError("""
- Python library not found. Set the \(Environment.library.key) \
- environment variable with the path to a Python library.
- """)
+ public enum Error: Swift.Error, Equatable, CustomStringConvertible {
+ case pythonLibraryNotFound
+
+ public var description: String {
+ switch self {
+ case .pythonLibraryNotFound:
+ return """
+ Python library not found. Set the \(Environment.library.key) \
+ environment variable with the path to a Python library.
+ """
+ }
+ }
}
- self.pythonLibraryHandle = pythonLibraryHandle
-
- // Check if Python is legacy (Python 2)
- isLegacyPython = PythonLibrary.loadSymbol(
- pythonLibraryHandle,
- PythonLibrary.pythonLegacySymbolName) != nil
- if isLegacyPython {
- PythonLibrary.log(
- "Loaded legacy Python library, using legacy symbols...")
+
+ private static let pythonInitializeSymbolName = "Py_Initialize"
+ private static let pythonLegacySymbolName = "PyString_AsString"
+
+#if canImport(Darwin)
+ private static let defaultLibraryHandle = UnsafeMutableRawPointer(bitPattern: -2) // RTLD_DEFAULT
+#else
+ private static let defaultLibraryHandle: UnsafeMutableRawPointer? = nil // RTLD_DEFAULT
+#endif
+
+ private static var isPythonLibraryLoaded = false
+ private static var _pythonLibraryHandle: UnsafeMutableRawPointer?
+ private static var pythonLibraryHandle: UnsafeMutableRawPointer? {
+ try! PythonLibrary.loadLibrary()
+ return self._pythonLibraryHandle
}
- PythonLibrary.librarySymbolsLoaded = true
- }
-
- static func loadSymbol(
- _ libraryHandle: UnsafeMutableRawPointer, _ name: String
- ) -> UnsafeMutableRawPointer? {
- #if canImport(Darwin) || canImport(Glibc)
- return dlsym(libraryHandle, name)
- #elseif os(Windows)
- let moduleHandle = libraryHandle
- .assumingMemoryBound(to: HINSTANCE__.self)
- let moduleSymbol = GetProcAddress(moduleHandle, name)
- return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self)
- #endif
- }
-
- static func loadSymbol(
- name: String, legacyName: String? = nil, type: T.Type = T.self
- ) -> T {
- var name = name
- if let legacyName = legacyName, PythonLibrary.shared.isLegacyPython {
- name = legacyName
+
+ /// Tries to load the Python library, will throw an error if no compatible library is found.
+ public static func loadLibrary() throws {
+ guard !self.isPythonLibraryLoaded else { return }
+ let pythonLibraryHandle = self.loadPythonLibrary()
+ guard self.isPythonLibraryLoaded(at: pythonLibraryHandle) else {
+ throw Error.pythonLibraryNotFound
+ }
+ self.isPythonLibraryLoaded = true
+ self._pythonLibraryHandle = pythonLibraryHandle
}
- log("Loading symbol '\(name)' from the Python library...")
- return unsafeBitCast(
- loadSymbol(PythonLibrary.shared.pythonLibraryHandle, name),
- to: type
- )
- }
-}
+ private static let isLegacyPython: Bool = {
+ let isLegacyPython = PythonLibrary.loadSymbol(PythonLibrary.pythonLibraryHandle, PythonLibrary.pythonLegacySymbolName) != nil
+ if isLegacyPython {
+ PythonLibrary.log("Loaded legacy Python library, using legacy symbols...")
+ }
+ return isLegacyPython
+ }()
+
+ internal static func loadSymbol(
+ name: String, legacyName: String? = nil, type: T.Type = T.self) -> T {
+ var name = name
+ if let legacyName = legacyName, self.isLegacyPython {
+ name = legacyName
+ }
-// Methods of `PythonLibrary` required to set a given Python version.
-public extension PythonLibrary {
- static func useVersion(_ major: Int, _ minor: Int? = nil) {
- precondition(!librarySymbolsLoaded, """
- Error: \(#function) should not be called after any Python library \
- has already been loaded.
- """)
- let version = PythonVersion(major: major, minor: minor)
- PythonLibrary.Environment.version.set(version.versionString)
- }
+ log("Loading symbol '\(name)' from the Python library...")
+ return unsafeBitCast(self.loadSymbol(self.pythonLibraryHandle, name), to: type)
+ }
}
-// `PythonVersion` struct that defines a given Python version.
-private extension PythonLibrary {
- struct PythonVersion {
- let major: Int
- let minor: Int?
+// Methods of `PythonLibrary` required to load the Python library.
+extension PythonLibrary {
+ private static let supportedMajorVersions: [Int] = [3, 2]
+ private static let supportedMinorVersions: [Int] = Array(0...30).reversed()
- static let versionSeparator: Character = "."
+ private static let libraryPathVersionCharacter: Character = ":"
- init(major: Int, minor: Int?) {
- self.major = major
- self.minor = minor
- }
+#if canImport(Darwin)
+ private static var libraryNames = ["Python.framework/Versions/:/Python"]
+ private static var libraryPathExtensions = [""]
+ private static var librarySearchPaths = ["", "/opt/homebrew/Frameworks/", "/usr/local/Frameworks/"]
+ private static var libraryVersionSeparator = "."
+#elseif os(Linux)
+ private static var libraryNames = ["libpython:", "libpython:m"]
+ private static var libraryPathExtensions = [".so"]
+ private static var librarySearchPaths = [""]
+ private static var libraryVersionSeparator = "."
+#elseif os(Windows)
+ private static var libraryNames = ["python:"]
+ private static var libraryPathExtensions = [".dll"]
+ private static var librarySearchPaths = [""]
+ private static var libraryVersionSeparator = ""
+#endif
- var versionString: String {
- var versionString = String(major)
- if let minor = minor {
- versionString += "\(PythonVersion.versionSeparator)\(minor)"
- }
- return versionString
- }
- }
-}
+ private static let libraryPaths: [String] = {
+ var libraryPaths: [String] = []
+ for librarySearchPath in librarySearchPaths {
+ for libraryName in libraryNames {
+ for libraryPathExtension in libraryPathExtensions {
+ let libraryPath =
+ librarySearchPath + libraryName + libraryPathExtension
+ libraryPaths.append(libraryPath)
+ }
+ }
+ }
+ return libraryPaths
+ }()
-// `PythonLibrary.Environment` enum used to read and set environment variables.
-private extension PythonLibrary {
- enum Environment: String {
- private static let keyPrefix = "PYTHON"
- private static let keySeparator = "_"
+ private static func loadSymbol(
+ _ libraryHandle: UnsafeMutableRawPointer?, _ name: String) -> UnsafeMutableRawPointer? {
+#if os(Windows)
+ guard let libraryHandle = libraryHandle else { return nil }
+ let moduleHandle = libraryHandle
+ .assumingMemoryBound(to: HINSTANCE__.self)
+ let moduleSymbol = GetProcAddress(moduleHandle, name)
+ return unsafeBitCast(moduleSymbol, to: UnsafeMutableRawPointer?.self)
+#else
+ return dlsym(libraryHandle, name)
+#endif
+ }
- case library = "LIBRARY"
- case version = "VERSION"
- case loaderLogging = "LOADER_LOGGING"
+ private static func isPythonLibraryLoaded(at pythonLibraryHandle: UnsafeMutableRawPointer? = nil) -> Bool {
+ let pythonLibraryHandle = pythonLibraryHandle ?? self.defaultLibraryHandle
+ return self.loadSymbol(pythonLibraryHandle, self.pythonInitializeSymbolName) != nil
+ }
- var key: String {
- return Environment.keyPrefix + Environment.keySeparator + rawValue
+ private static func loadPythonLibrary() -> UnsafeMutableRawPointer? {
+ let pythonLibraryHandle: UnsafeMutableRawPointer?
+ if self.isPythonLibraryLoaded() {
+ pythonLibraryHandle = self.defaultLibraryHandle
+ }
+ else if let pythonLibraryPath = Environment.library.value {
+ pythonLibraryHandle = self.loadPythonLibrary(at: pythonLibraryPath)
+ }
+ else {
+ pythonLibraryHandle = self.findAndLoadExternalPythonLibrary()
+ }
+ return pythonLibraryHandle
}
- var value: String? {
- guard let value = getenv(key) else { return nil }
- return String(cString: value)
+ private static func findAndLoadExternalPythonLibrary() -> UnsafeMutableRawPointer? {
+ for majorVersion in supportedMajorVersions {
+ for minorVersion in supportedMinorVersions {
+ for libraryPath in libraryPaths {
+ let version = PythonVersion(major: majorVersion, minor: minorVersion)
+ guard let pythonLibraryHandle = loadPythonLibrary(
+ at: libraryPath, version: version) else {
+ continue
+ }
+ return pythonLibraryHandle
+ }
+ }
+ }
+ return nil
}
- func set(_ value: String) {
- #if canImport(Darwin) || canImport(Glibc)
- setenv(key, value, 1)
- #elseif os(Windows)
- _putenv_s(key, value)
- #endif
+ private static func loadPythonLibrary(
+ at path: String, version: PythonVersion) -> UnsafeMutableRawPointer? {
+ let versionString = version.versionString
+
+ if let requiredPythonVersion = Environment.version.value {
+ let requiredMajorVersion = Int(requiredPythonVersion)
+ if requiredPythonVersion != versionString,
+ requiredMajorVersion != version.major {
+ return nil
+ }
+ }
+
+ let libraryVersionString = versionString
+ .split(separator: PythonVersion.versionSeparator)
+ .joined(separator: libraryVersionSeparator)
+ let path = path.split(separator: libraryPathVersionCharacter)
+ .joined(separator: libraryVersionString)
+ return self.loadPythonLibrary(at: path)
+ }
+
+ private static func loadPythonLibrary(at path: String) -> UnsafeMutableRawPointer? {
+ self.log("Trying to load library at '\(path)'...")
+#if os(Windows)
+ let pythonLibraryHandle = UnsafeMutableRawPointer(LoadLibraryA(path))
+#else
+ // Must be RTLD_GLOBAL because subsequent .so files from the imported python
+ // modules may depend on this .so file.
+ let pythonLibraryHandle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL)
+ if pythonLibraryHandle == nil {
+ self.log("Failed to load library at '\(path)'.")
+ if let errorCString = dlerror() {
+ let errorString = String(cString: errorCString)
+ self.log("Reason for failure: \(errorString)")
+ }
+ }
+#endif
+
+ if pythonLibraryHandle != nil {
+ self.log("Library at '\(path)' was successfully loaded.")
+ }
+ return pythonLibraryHandle
}
- }
}
-// Methods of `PythonLibrary` required to load the Python library.
-private extension PythonLibrary {
- static let supportedMajorVersions: [Int] = [3, 2]
- static let supportedMinorVersions: [Int] = Array(0...30).reversed()
-
- static let libraryPathVersionCharacter: Character = ":"
-
- #if canImport(Darwin)
- static var libraryNames = ["Python.framework/Versions/:/Python"]
- static var libraryPathExtensions = [""]
- static var librarySearchPaths = ["", "/usr/local/Frameworks/"]
- static var libraryVersionSeparator = "."
- #elseif os(Linux)
- static var libraryNames = ["libpython:", "libpython:m"]
- static var libraryPathExtensions = [".so"]
- static var librarySearchPaths = [""]
- static var libraryVersionSeparator = "."
- #elseif os(Windows)
- static var libraryNames = ["python:"]
- static var libraryPathExtensions = [".dll"]
- static var librarySearchPaths = [""]
- static var libraryVersionSeparator = ""
- #endif
-
- static let libraryPaths: [String] = {
- var libraryPaths: [String] = []
- for librarySearchPath in librarySearchPaths {
- for libraryName in libraryNames {
- for libraryPathExtension in libraryPathExtensions {
- let libraryPath =
- librarySearchPath + libraryName + libraryPathExtension
- libraryPaths.append(libraryPath)
- }
- }
+// Methods of `PythonLibrary` required to set a given Python version or library path.
+extension PythonLibrary {
+ private static func enforceNonLoadedPythonLibrary(function: String = #function) {
+ precondition(!self.isPythonLibraryLoaded, """
+ Error: \(function) should not be called after any Python library \
+ has already been loaded.
+ """)
}
- return libraryPaths
- }()
- static func loadPythonLibrary() -> UnsafeMutableRawPointer? {
- if let pythonLibraryPath = Environment.library.value {
- return loadPythonLibrary(at: pythonLibraryPath)
+ /// Use the Python library with the specified version.
+ /// - Parameters:
+ /// - major: Major version or nil to use any Python version.
+ /// - minor: Minor version or nil to use any minor version.
+ public static func useVersion(_ major: Int?, _ minor: Int? = nil) {
+ self.enforceNonLoadedPythonLibrary()
+ let version = PythonVersion(major: major, minor: minor)
+ PythonLibrary.Environment.version.set(version.versionString)
}
- for majorVersion in supportedMajorVersions {
- for minorVersion in supportedMinorVersions {
- for libraryPath in libraryPaths {
- let version = PythonVersion(major: majorVersion, minor: minorVersion)
- guard let pythonLibraryHandle = loadPythonLibrary(
- at: libraryPath, version: version) else {
- continue
- }
- return pythonLibraryHandle
- }
- }
+ /// Use the Python library at the specified path.
+ /// - Parameter path: Path of the Python library to load or nil to use the default search path.
+ public static func useLibrary(at path: String?) {
+ self.enforceNonLoadedPythonLibrary()
+ PythonLibrary.Environment.library.set(path ?? "")
}
- return nil
- }
-
- static func loadPythonLibrary(
- at path: String, version: PythonVersion
- ) -> UnsafeMutableRawPointer? {
- let versionString = version.versionString
-
- if let requiredPythonVersion = Environment.version.value {
- let requiredMajorVersion = Int(requiredPythonVersion)
- if requiredPythonVersion != versionString,
- requiredMajorVersion != version.major {
- return nil
- }
+}
+
+// `PythonVersion` struct that defines a given Python version.
+extension PythonLibrary {
+ private struct PythonVersion {
+ let major: Int?
+ let minor: Int?
+
+ static let versionSeparator: Character = "."
+
+ init(major: Int?, minor: Int?) {
+ precondition(!(major == nil && minor != nil), """
+ Error: The Python library minor version cannot be specified \
+ without the major version.
+ """)
+ self.major = major
+ self.minor = minor
+ }
+
+ var versionString: String {
+ guard let major = major else { return "" }
+ var versionString = String(major)
+ if let minor = minor {
+ versionString += "\(PythonVersion.versionSeparator)\(minor)"
+ }
+ return versionString
+ }
}
+}
+
+// `PythonLibrary.Environment` enum used to read and set environment variables.
+extension PythonLibrary {
+ private enum Environment: String {
+ private static let keyPrefix = "PYTHON"
+ private static let keySeparator = "_"
+
+ case library = "LIBRARY"
+ case version = "VERSION"
+ case loaderLogging = "LOADER_LOGGING"
- let libraryVersionString = versionString
- .split(separator: PythonVersion.versionSeparator)
- .joined(separator: libraryVersionSeparator)
- let path = path.split(separator: libraryPathVersionCharacter)
- .joined(separator: libraryVersionString)
- return loadPythonLibrary(at: path)
- }
-
- static func loadPythonLibrary(at path: String) -> UnsafeMutableRawPointer? {
- log("Trying to load library at '\(path)'...")
- #if canImport(Darwin) || canImport(Glibc)
- // Must be RTLD_GLOBAL because subsequent .so files from the imported python
- // modules may depend on this .so file.
- let pythonLibraryHandle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL)
- #elseif os(Windows)
- let pythonLibraryHandle = UnsafeMutableRawPointer(LoadLibraryA(path))
- #endif
-
- if pythonLibraryHandle != nil {
- log("Library at '\(path)' was sucessfully loaded.")
+ var key: String {
+ return Environment.keyPrefix + Environment.keySeparator + rawValue
+ }
+
+ var value: String? {
+ guard let cString = getenv(key) else { return nil }
+ let value = String(cString: cString)
+ guard !value.isEmpty else { return nil }
+ return value
+ }
+
+ func set(_ value: String) {
+#if os(Windows)
+ _putenv_s(key, value)
+#else
+ setenv(key, value, 1)
+#endif
+ }
}
- return pythonLibraryHandle
- }
}
// Methods of `PythonLibrary` used for logging messages.
-private extension PythonLibrary {
- static func log(_ message: String) {
- guard Environment.loaderLogging.value != nil else { return }
- fputs(message + "\n", stderr)
- }
+extension PythonLibrary {
+ private static func log(_ message: String) {
+ guard Environment.loaderLogging.value != nil else { return }
+ fputs(message + "\n", stderr)
+ }
}
diff --git a/PythonTool/Info.plist b/PythonTool/Info.plist
deleted file mode 100644
index 4a83cc1..0000000
--- a/PythonTool/Info.plist
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
- NSHumanReadableCopyright
- Copyright © 2017 Pedro José Pereira Vieito.
-
-
diff --git a/PythonTool/OperatingSystemVersion.swift b/PythonTool/OperatingSystemVersion.swift
deleted file mode 100644
index 26c852d..0000000
--- a/PythonTool/OperatingSystemVersion.swift
+++ /dev/null
@@ -1,20 +0,0 @@
-//
-// OperatingSystemVersion.swift
-// PythonTool
-//
-// Created by Pedro José Pereira Vieito on 29/1/18.
-// Copyright © 2018 Pedro José Pereira Vieito. All rights reserved.
-//
-
-import Foundation
-
-extension OperatingSystemVersion: CustomStringConvertible {
-
- public var shortVersion: String {
- return "\(self.majorVersion).\(self.minorVersion)"
- }
-
- public var description: String {
- return "\(self.majorVersion).\(self.minorVersion).\(self.patchVersion)"
- }
-}
diff --git a/PythonTool/main.swift b/PythonTool/main.swift
deleted file mode 100644
index ddb0f1c..0000000
--- a/PythonTool/main.swift
+++ /dev/null
@@ -1,90 +0,0 @@
-//
-// main.swift
-// PythonTool
-//
-// Created by Pedro José Pereira Vieito on 29/1/18.
-// Copyright © 2018 Pedro José Pereira Vieito. All rights reserved.
-//
-
-import Foundation
-import LoggerKit
-import CommandLineKit
-import PythonKit
-
-let listOption = BoolOption(shortFlag: "l", longFlag: "list", helpMessage: "List installed modules.")
-let pathOption = BoolOption(shortFlag: "p", longFlag: "path", helpMessage: "List Python paths.")
-let verboseOption = BoolOption(shortFlag: "v", longFlag: "verbose", helpMessage: "Verbose mode.")
-let helpOption = BoolOption(shortFlag: "h", longFlag: "help", helpMessage: "Prints a help message.")
-
-let cli = CommandLineKit.CommandLine()
-cli.addOptions(listOption, pathOption, verboseOption, helpOption)
-
-do {
- try cli.parse(strict: true)
-}
-catch {
- cli.printUsage(error)
- exit(EX_USAGE)
-}
-
-if helpOption.value {
- cli.printUsage()
- exit(-1)
-}
-
-Logger.logMode = .commandLine
-Logger.logLevel = verboseOption.value ? .debug : .info
-
-do {
- let pythonVersion = OperatingSystemVersion(
- majorVersion: Int(Python.versionInfo.major) ?? 0,
- minorVersion: Int(Python.versionInfo.minor) ?? 0,
- patchVersion: Int(Python.versionInfo.micro) ?? 0
- )
-
- Logger.log(important: "Python \(pythonVersion.shortVersion)")
- Logger.log(info: "Version: \(pythonVersion)")
- Logger.log(verbose: "Version String: \(Python.version.splitlines()[0])")
-
- let sys = try Python.attemptImport("sys")
-
- Logger.log(verbose: "Executable: \(sys.executable)")
- Logger.log(verbose: "Executable Prefix: \(sys.exec_prefix)")
-
- if pathOption.value {
-
- Logger.log(important: "Python Paths (\(sys.path.count))")
-
- if !sys.path.isEmpty {
- for searchPath in sys.path {
- Logger.log(success: searchPath)
- }
- }
- else {
- Logger.log(warning: "No paths available.")
- }
- }
-
- if listOption.value {
-
- let pkg_resources = try Python.attemptImport("pkg_resources")
-
- let installedModules = Dictionary(pkg_resources.working_set.by_key)!
-
- Logger.log(important: "Python Modules (\(installedModules.count))")
-
- if !installedModules.isEmpty {
- let installedModulesArray = installedModules.sorted { $0.key.lowercased() < $1.key.lowercased() }
-
- for (_, moduleReference) in installedModulesArray {
- Logger.log(success: "\(moduleReference.key) (\(moduleReference.version))")
- }
- }
- else {
- Logger.log(warning: "No modules available.")
- }
- }
-}
-catch {
- Logger.log(fatalError: error)
-}
diff --git a/README.md b/README.md
index e433908..8174faa 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
-# PythonKit
+# PythonKit
-Swift framework to interact with Python.
-
-## Requirements
+[](https://swiftpackageindex.com/pvieito/PythonKit)
+[](https://swiftpackageindex.com/pvieito/PythonKit)
-`PythonKit` requires [**Swift 5**](https://swift.org/download/) or higher and has been tested on macOS, Linux and Windows.
+Swift framework to interact with Python.
## Usage
@@ -18,12 +17,12 @@ print(f"Python Version: {sys.version}")
print(f"Python Encoding: {sys.getdefaultencoding().upper()}")
```
-Can be implemented in Swift through PythonKit with the following code:
+The same code can be implemented in Swift through PythonKit:
```swift
import PythonKit
-let sys = try Python.import("sys")
+let sys = Python.import("sys")
print("Python \(sys.version_info.major).\(sys.version_info.minor)")
print("Python Version: \(sys.version)")
@@ -35,37 +34,39 @@ print("Python Encoding: \(sys.getdefaultencoding().upper())")
Add the following dependency to your `Package.swift` manifest:
```swift
-.package(url: "https://github.com/pvieito/PythonKit.git", .branch("master")),
+.package(url: "https://github.com/pvieito/PythonKit.git", from: "1.0.0"),
```
-## Build
-
-`PythonKit` can be built with Swift PM:
-
-```
-$ cd PythonKit
-$ swift run
-[*] Python 3.7
-[ ] Version: 3.7.0
-```
+## Environment Variables
-The Python library will be loaded at runtime, `PythonKit` 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`.
+As Python libraries 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 a specific Python library path or name with `PYTHON_LIBRARY`.
-```
+```console
$ PYTHON_VERSION=3 swift run
[*] Python 3.5
-[ ] Version: 3.5.2
$ PYTHON_VERSION=2.7 swift run
[*] Python 2.7
-[ ] Version: 2.7.10
$ PYTHON_LIBRARY=libpython3.5.so swift run
[*] Python 3.5
-[ ] Version: 3.5.2
$ PYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython2.7.so swift run
[*] Python 2.7
-[ ] Version: 2.7.10
```
+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:
+
+```console
+$ PYTHON_LOADER_LOGGING=TRUE PYTHON_VERSION=3.8 swift run
+Loading symbol 'Py_Initialize' from the Python library...
+Trying to load library at 'Python.framework/Versions/3.8/Python'...
+Trying to load library at '/usr/local/Frameworks/Python.framework/Versions/3.8/Python'...
+Fatal error: Python library not found. Set the PYTHON_LIBRARY environment variable with the path to a Python library.
+```
+
+## Troubleshooting
+
+- If you 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.
+
## Notes
-`PythonKit` is based on the `Python` module from the [Swift for TensorFlow](https://github.com/tensorflow/swift) project. Currently both modules are equivalent, the mantainers have been developing them jointly and are kept in sync.
+- Originally **PythonKit** was based on the `Python` module from the [**Swift for TensorFlow**](https://github.com/tensorflow/swift) experimental project.
+- If you have questions about **PythonKit** you can ask on the [**Swift Forums**](https://forums.swift.org/c/related-projects/).
diff --git a/Tests/PythonKitTests/Info.plist b/Tests/PythonKitTests/Info.plist
deleted file mode 100644
index 6c40a6c..0000000
--- a/Tests/PythonKitTests/Info.plist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- BNDL
- CFBundleShortVersionString
- 1.0
- CFBundleVersion
- 1
-
-
diff --git a/Tests/PythonKitTests/NumpyConversionTests.swift b/Tests/PythonKitTests/NumpyConversionTests.swift
index d798cca..4189470 100644
--- a/Tests/PythonKitTests/NumpyConversionTests.swift
+++ b/Tests/PythonKitTests/NumpyConversionTests.swift
@@ -2,31 +2,31 @@ import XCTest
import PythonKit
class NumpyConversionTests: XCTestCase {
- static var numpyModule = try? Python.attemptImport("numpy")
+ static var numpyModule = try? Python.attemptImport("numpy")
- func testArrayConversion() {
- guard let np = NumpyConversionTests.numpyModule else { return }
+ func testArrayConversion() {
+ guard let np = NumpyConversionTests.numpyModule else { return }
- let numpyArrayEmpty = np.array([] as [Float], dtype: np.float32)
- XCTAssertEqual([], Array(numpy: numpyArrayEmpty))
+ let numpyArrayEmpty = np.array([] as [Float], dtype: np.float32)
+ XCTAssertEqual([], Array(numpy: numpyArrayEmpty))
- let numpyArrayBool = np.array([true, false, false, true])
- XCTAssertEqual([true, false, false, true], Array(numpy: numpyArrayBool))
+ let numpyArrayBool = np.array([true, false, false, true])
+ XCTAssertEqual([true, false, false, true], Array(numpy: numpyArrayBool))
- let numpyArrayFloat = np.ones([6], dtype: np.float32)
- XCTAssertEqual(Array(repeating: 1, count: 6), Array(numpy: numpyArrayFloat))
+ let numpyArrayFloat = np.ones([6], dtype: np.float32)
+ XCTAssertEqual(Array(repeating: 1, count: 6), Array(numpy: numpyArrayFloat))
- let numpyArrayInt32 = np.array([-1, 4, 25, 2018], dtype: np.int32)
- XCTAssertEqual([-1, 4, 25, 2018], Array(numpy: numpyArrayInt32))
+ let numpyArrayInt32 = np.array([-1, 4, 25, 2018], dtype: np.int32)
+ XCTAssertEqual([-1, 4, 25, 2018], Array(numpy: numpyArrayInt32))
- let numpyArray2D = np.ones([2, 3])
- XCTAssertNil(Array(numpy: numpyArray2D))
+ let numpyArray2D = np.ones([2, 3])
+ XCTAssertNil(Array(numpy: numpyArray2D))
- let numpyArrayStrided = np.array([[1, 2], [1, 2]], dtype: np.int32)[
- Python.slice(Python.None), 1]
- // Assert that the array has a stride, so that we're certainly testing a
- // strided array.
- XCTAssertNotEqual(numpyArrayStrided.__array_interface__["strides"], Python.None)
- XCTAssertEqual([2, 2], Array(numpy: numpyArrayStrided))
- }
+ let numpyArrayStrided = np.array([[1, 2], [1, 2]], dtype: np.int32)[
+ Python.slice(Python.None), 1]
+ // Assert that the array has a stride, so that we're certainly testing a
+ // strided array.
+ XCTAssertNotEqual(numpyArrayStrided.__array_interface__["strides"], Python.None)
+ XCTAssertEqual([2, 2], Array(numpy: numpyArrayStrided))
+ }
}
diff --git a/Tests/PythonKitTests/PythonFunctionTests.swift b/Tests/PythonKitTests/PythonFunctionTests.swift
new file mode 100644
index 0000000..f11ad57
--- /dev/null
+++ b/Tests/PythonKitTests/PythonFunctionTests.swift
@@ -0,0 +1,343 @@
+import XCTest
+import PythonKit
+
+class PythonFunctionTests: XCTestCase {
+ private var canUsePythonFunction: Bool {
+ let versionMajor = Python.versionInfo.major
+ let versionMinor = Python.versionInfo.minor
+ return (versionMajor == 3 && versionMinor >= 1) || versionMajor > 3
+ }
+
+ func testPythonFunction() {
+ guard canUsePythonFunction else {
+ return
+ }
+
+ let pythonAdd = PythonFunction { args in
+ let lhs = args[0]
+ let rhs = args[1]
+ return lhs + rhs
+ }.pythonObject
+
+ let pythonSum = pythonAdd(2, 3)
+ XCTAssertNotNil(Double(pythonSum))
+ XCTAssertEqual(pythonSum, 5)
+
+ // Test function with keyword arguments
+
+ // Since there is no alternative function signature, `args` and `kwargs`
+ // can be used without manually stating their type. This differs from
+ // the behavior when there are no keywords.
+ let pythonSelect = PythonFunction { args, kwargs in
+ // NOTE: This may fail on Python versions before 3.6 because they do
+ // not preserve order of keyword arguments
+ XCTAssertEqual(args[0], true)
+ XCTAssertEqual(kwargs[0].key, "y")
+ XCTAssertEqual(kwargs[0].value, 2)
+ XCTAssertEqual(kwargs[1].key, "x")
+ XCTAssertEqual(kwargs[1].value, 3)
+
+ let conditional = Bool(args[0])!
+ let xIndex = kwargs.firstIndex(where: { $0.key == "x" })!
+ let yIndex = kwargs.firstIndex(where: { $0.key == "y" })!
+
+ return kwargs[conditional ? xIndex : yIndex].value
+ }.pythonObject
+
+ let pythonSelectOutput = pythonSelect(true, y: 2, x: 3)
+ XCTAssertEqual(pythonSelectOutput, 3)
+ }
+
+ // From https://www.geeksforgeeks.org/create-classes-dynamically-in-python
+ func testPythonClassConstruction() {
+ guard canUsePythonFunction else {
+ return
+ }
+
+ let constructor = PythonInstanceMethod { args in
+ let `self` = args[0]
+ `self`.constructor_arg = args[1]
+ return Python.None
+ }
+
+ // Instead of calling `print`, use this to test what would be output.
+ var printOutput: String?
+
+ // Example of function using an alternative syntax for `args`.
+ let displayMethod = PythonInstanceMethod { (args: [PythonObject]) in
+ // let `self` = args[0]
+ printOutput = String(args[1])
+ return Python.None
+ }
+
+ let classMethodOriginal = PythonInstanceMethod { args in
+ // let cls = args[0]
+ printOutput = String(args[1])
+ return Python.None
+ }
+
+ // Did not explicitly convert `constructor` or `displayMethod` to
+ // PythonObject. This is intentional, as the `PythonClass` initializer
+ // should take any `PythonConvertible` and not just `PythonObject`.
+ let classMethod = Python.classmethod(classMethodOriginal.pythonObject)
+
+ let Geeks = PythonClass("Geeks", members: [
+ // Constructor
+ "__init__": constructor,
+
+ // Data members
+ "string_attribute": "Geeks 4 geeks!",
+ "int_attribute": 1706256,
+
+ // Member functions
+ "func_arg": displayMethod,
+ "class_func": classMethod,
+ ]).pythonObject
+
+ let obj = Geeks("constructor argument")
+ XCTAssertEqual(obj.constructor_arg, "constructor argument")
+ XCTAssertEqual(obj.string_attribute, "Geeks 4 geeks!")
+ XCTAssertEqual(obj.int_attribute, 1706256)
+
+ obj.func_arg("Geeks for Geeks")
+ XCTAssertEqual(printOutput, "Geeks for Geeks")
+
+ Geeks.class_func("Class Dynamically Created!")
+ XCTAssertEqual(printOutput, "Class Dynamically Created!")
+ }
+
+ // Previously, there was a build error where passing a simple
+ // `PythonClass.Members` literal made the literal's type ambiguous. It was
+ // confused with `[String: PythonObject]`. The solution was adding a
+ // `@_disfavoredOverload` attribute to the more specific initializer.
+ func testPythonClassInitializer() {
+ guard canUsePythonFunction else {
+ return
+ }
+
+ let MyClass = PythonClass(
+ "MyClass",
+ superclasses: [Python.object],
+ members: [
+ "memberName": "memberValue",
+ ]
+ ).pythonObject
+
+ let memberValue = MyClass().memberName
+ XCTAssertEqual(String(memberValue), "memberValue")
+ }
+
+ func testPythonClassInheritance() {
+ guard canUsePythonFunction else {
+ return
+ }
+
+ var helloOutput: String?
+ var helloWorldOutput: String?
+
+ // Declare subclasses of `Python.Exception`
+
+ let HelloException = PythonClass(
+ "HelloException",
+ superclasses: [Python.Exception],
+ members: [
+ "str_prefix": "HelloException-prefix ",
+
+ "__init__": PythonInstanceMethod { args in
+ let `self` = args[0]
+ let message = "hello \(args[1])"
+ helloOutput = String(message)
+
+ // Conventional `super` syntax does not work; use this instead.
+ Python.Exception.__init__(`self`, message)
+ return Python.None
+ },
+
+ // Example of function using the `self` convention instead of `args`.
+ "__str__": PythonInstanceMethod { (`self`: PythonObject) in
+ return `self`.str_prefix + Python.repr(`self`)
+ }
+ ]
+ ).pythonObject
+
+ let HelloWorldException = PythonClass(
+ "HelloWorldException",
+ superclasses: [HelloException],
+ members: [
+ "str_prefix": "HelloWorldException-prefix ",
+
+ "__init__": PythonInstanceMethod { args in
+ let `self` = args[0]
+ let message = "world \(args[1])"
+ helloWorldOutput = String(message)
+
+ `self`.int_param = args[2]
+
+ // Conventional `super` syntax does not work; use this instead.
+ HelloException.__init__(`self`, message)
+ return Python.None
+ },
+
+ // Example of function using the `self` convention instead of `args`.
+ "custom_method": PythonInstanceMethod { (`self`: PythonObject) in
+ return `self`.int_param
+ }
+ ]
+ ).pythonObject
+
+ // Test that inheritance works as expected
+
+ let error1 = HelloException("test 1")
+ XCTAssertEqual(helloOutput, "hello test 1")
+ XCTAssertEqual(Python.str(error1), "HelloException-prefix HelloException('hello test 1')")
+ XCTAssertEqual(Python.repr(error1), "HelloException('hello test 1')")
+
+ let error2 = HelloWorldException("test 1", 123)
+ XCTAssertEqual(helloOutput, "hello world test 1")
+ XCTAssertEqual(helloWorldOutput, "world test 1")
+ XCTAssertEqual(Python.str(error2), "HelloWorldException-prefix HelloWorldException('hello world test 1')")
+ XCTAssertEqual(Python.repr(error2), "HelloWorldException('hello world test 1')")
+ XCTAssertEqual(error2.custom_method(), 123)
+ XCTAssertNotEqual(error2.custom_method(), "123")
+
+ // Test that subclasses behave like Python exceptions
+
+ // Example of function with no named parameters, which can be stated
+ // ergonomically using an underscore. The ignored input is a [PythonObject].
+ let _ = PythonFunction { _ in
+ throw HelloWorldException("EXAMPLE ERROR MESSAGE", 2)
+ }.pythonObject
+
+ /*
+ do {
+ try testFunction.throwing.dynamicallyCall(withArguments: [])
+ XCTFail("testFunction did not throw an error.")
+ } catch PythonError.exception(let error, _) {
+ guard let description = String(error) else {
+ XCTFail("A string could not be created from a HelloWorldException.")
+ return
+ }
+
+ XCTAssertTrue(description.contains("EXAMPLE ERROR MESSAGE"))
+ XCTAssertTrue(description.contains("HelloWorldException"))
+ } catch {
+ XCTFail("Got error that was not a Python exception: \(error.localizedDescription)")
+ }
+ */
+ }
+
+ // Tests the ability to dynamically construct an argument list with keywords
+ // and instantiate a `PythonInstanceMethod` with keywords.
+ func testPythonClassInheritanceWithKeywords() {
+ guard canUsePythonFunction else {
+ return
+ }
+
+ func getValue(key: String, kwargs: [(String, PythonObject)]) -> PythonObject {
+ let index = kwargs.firstIndex(where: { $0.0 == key })!
+ return kwargs[index].1
+ }
+
+ // Base class has the following arguments:
+ // __init__():
+ // - 1 unnamed argument
+ // - param1
+ // - param2
+ //
+ // test_method():
+ // - param1
+ // - param2
+
+ let BaseClass = PythonClass(
+ "BaseClass",
+ superclasses: [],
+ members: [
+ "__init__": PythonInstanceMethod { args, kwargs in
+ let `self` = args[0]
+ `self`.arg1 = args[1]
+ `self`.param1 = getValue(key: "param1", kwargs: kwargs)
+ `self`.param2 = getValue(key: "param2", kwargs: kwargs)
+ return Python.None
+ },
+
+ "test_method": PythonInstanceMethod { args, kwargs in
+ let `self` = args[0]
+ `self`.param1 += getValue(key: "param1", kwargs: kwargs)
+ `self`.param2 += getValue(key: "param2", kwargs: kwargs)
+ return Python.None
+ }
+ ]
+ ).pythonObject
+
+ // Derived class accepts the following arguments:
+ // __init__():
+ // - param2
+ // - param3
+ //
+ // test_method():
+ // - param1
+ // - param2
+ // - param3
+
+ let DerivedClass = PythonClass(
+ "DerivedClass",
+ superclasses: [],
+ members: [
+ "__init__": PythonInstanceMethod { args, kwargs in
+ let `self` = args[0]
+ `self`.param3 = getValue(key: "param3", kwargs: kwargs)
+
+ // Lists the arguments in an order different than they are
+ // specified (self, param2, param3, param1, arg1). The
+ // correct order is (self, arg1, param1, param2, param3).
+ let newKeywordArguments = args.map {
+ ("", $0)
+ } + kwargs + [
+ ("param1", 1),
+ ("", 0)
+ ]
+
+ BaseClass.__init__.dynamicallyCall(
+ withKeywordArguments: newKeywordArguments)
+ return Python.None
+ },
+
+ "test_method": PythonInstanceMethod { args, kwargs in
+ let `self` = args[0]
+ `self`.param3 += getValue(key: "param3", kwargs: kwargs)
+
+ BaseClass.test_method.dynamicallyCall(
+ withKeywordArguments: args.map { ("", $0) } + kwargs)
+ return Python.None
+ }
+ ]
+ ).pythonObject
+
+ let derivedInstance = DerivedClass(param2: 2, param3: 3)
+ XCTAssertEqual(derivedInstance.arg1, 0)
+ XCTAssertEqual(derivedInstance.param1, 1)
+ XCTAssertEqual(derivedInstance.param2, 2)
+ XCTAssertEqual(derivedInstance.checking.param3, 3)
+
+ derivedInstance.test_method(param1: 1, param2: 2, param3: 3)
+ XCTAssertEqual(derivedInstance.arg1, 0)
+ XCTAssertEqual(derivedInstance.param1, 2)
+ XCTAssertEqual(derivedInstance.param2, 4)
+ XCTAssertEqual(derivedInstance.checking.param3, 6)
+
+ // Validate that subclassing and instantiating the derived class does
+ // not affect behavior of the parent class.
+
+ let baseInstance = BaseClass(0, param1: 10, param2: 20)
+ XCTAssertEqual(baseInstance.arg1, 0)
+ XCTAssertEqual(baseInstance.param1, 10)
+ XCTAssertEqual(baseInstance.param2, 20)
+ XCTAssertEqual(baseInstance.checking.param3, nil)
+
+ baseInstance.test_method(param1: 10, param2: 20)
+ XCTAssertEqual(baseInstance.arg1, 0)
+ XCTAssertEqual(baseInstance.param1, 20)
+ XCTAssertEqual(baseInstance.param2, 40)
+ XCTAssertEqual(baseInstance.checking.param3, nil)
+ }
+}
diff --git a/Tests/PythonKitTests/PythonRuntimeTests.swift b/Tests/PythonKitTests/PythonRuntimeTests.swift
index 6eb3c24..90f1418 100644
--- a/Tests/PythonKitTests/PythonRuntimeTests.swift
+++ b/Tests/PythonKitTests/PythonRuntimeTests.swift
@@ -2,271 +2,382 @@ import XCTest
import PythonKit
class PythonRuntimeTests: XCTestCase {
- func testCheckVersion() {
- XCTAssertGreaterThanOrEqual(Python.versionInfo.major, 2)
- XCTAssertGreaterThanOrEqual(Python.versionInfo.minor, 0)
- }
-
- func testPythonList() {
- let list: PythonObject = [0, 1, 2]
- XCTAssertEqual("[0, 1, 2]", list.description)
- XCTAssertEqual(3, Python.len(list))
- XCTAssertEqual("[0, 1, 2]", Python.str(list))
-
- let polymorphicList = PythonObject(["a", 2, true, 1.5])
- XCTAssertEqual("a", polymorphicList[0])
- XCTAssertEqual(2, polymorphicList[1])
- XCTAssertEqual(true, polymorphicList[2])
- XCTAssertEqual(1.5, polymorphicList[3])
- XCTAssertEqual(1.5, polymorphicList[-1])
-
- polymorphicList[2] = 2
- XCTAssertEqual(2, polymorphicList[2])
- }
-
- func testPythonDict() {
- let dict: PythonObject = ["a": 1, 1: 0.5]
- XCTAssertEqual(2, Python.len(dict))
- XCTAssertEqual(1, dict["a"])
- XCTAssertEqual(0.5, dict[1])
-
- dict["b"] = "c"
- XCTAssertEqual("c", dict["b"])
- dict["b"] = "d"
- XCTAssertEqual("d", dict["b"])
- }
-
- func testRange() {
- let slice = PythonObject(5..<10)
- XCTAssertEqual(Python.slice(5, 10), slice)
- XCTAssertEqual(5, slice.start)
- XCTAssertEqual(10, slice.stop)
-
- let range = Range(slice)
- XCTAssertNotNil(range)
- XCTAssertEqual(5, range?.lowerBound)
- XCTAssertEqual(10, range?.upperBound)
-
- XCTAssertNil(Range(PythonObject(5...)))
- }
-
- func testPartialRangeFrom() {
- let slice = PythonObject(5...)
- XCTAssertEqual(Python.slice(5, Python.None), slice)
- XCTAssertEqual(5, slice.start)
-
- let range = PartialRangeFrom(slice)
- XCTAssertNotNil(range)
- XCTAssertEqual(5, range?.lowerBound)
-
- XCTAssertNil(PartialRangeFrom(PythonObject(..<5)))
- }
-
- func testPartialRangeUpTo() {
- let slice = PythonObject(..<5)
- XCTAssertEqual(Python.slice(5), slice)
- XCTAssertEqual(5, slice.stop)
-
- let range = PartialRangeUpTo(slice)
- XCTAssertNotNil(range)
- XCTAssertEqual(5, range?.upperBound)
-
- XCTAssertNil(PartialRangeUpTo(PythonObject(5...)))
- }
-
- func testStrideable() {
- let strideTo = stride(from: PythonObject(0), to: 100, by: 2)
- XCTAssertEqual(0, strideTo.min()!)
- XCTAssertEqual(98, strideTo.max()!)
- XCTAssertEqual([0, 2, 4, 6, 8], Array(strideTo.prefix(5)))
- XCTAssertEqual([90, 92, 94, 96, 98], Array(strideTo.suffix(5)))
-
- let strideThrough = stride(from: PythonObject(0), through: 100, by: 2)
- XCTAssertEqual(0, strideThrough.min()!)
- XCTAssertEqual(100, strideThrough.max()!)
- XCTAssertEqual([0, 2, 4, 6, 8], Array(strideThrough.prefix(5)))
- XCTAssertEqual([92, 94, 96, 98, 100], Array(strideThrough.suffix(5)))
- }
-
- func testBinaryOps() {
- XCTAssertEqual(42, PythonObject(42))
- XCTAssertEqual(42, PythonObject(2) + PythonObject(40))
- XCTAssertEqual(2, PythonObject(2) * PythonObject(3) + PythonObject(-4))
-
- XCTAssertEqual("abcdef", PythonObject("ab") +
- PythonObject("cde") +
- PythonObject("") +
- PythonObject("f"))
- XCTAssertEqual("ababab", PythonObject("ab") * 3)
-
- var x = PythonObject(2)
- x += 3
- XCTAssertEqual(5, x)
- x *= 2
- XCTAssertEqual(10, x)
- x -= 3
- XCTAssertEqual(7, x)
- x /= 2
- XCTAssertEqual(3.5, x)
- x += -1
- XCTAssertEqual(2.5, x)
- }
-
- func testComparable() {
- let array: [PythonObject] = [-1, 10, 1, 0, 0]
- XCTAssertEqual([-1, 0, 0, 1, 10], array.sorted())
- let list: PythonObject = [-1, 10, 1, 0, 0]
- XCTAssertEqual([-1, 0, 0, 1, 10], list.sorted())
- }
-
- func testHashable() {
- func compareHashValues(_ x: PythonConvertible) {
- let a = x.pythonObject
- let b = x.pythonObject
- XCTAssertEqual(a.hashValue, b.hashValue)
+ func testCheckVersion() {
+ XCTAssertGreaterThanOrEqual(Python.versionInfo.major, 2)
+ XCTAssertGreaterThanOrEqual(Python.versionInfo.minor, 0)
}
- compareHashValues(1)
- compareHashValues(3.14)
- compareHashValues("asdf")
- compareHashValues(PythonObject(tupleOf: 1, 2, 3))
- }
+ func testPythonList() {
+ let list: PythonObject = [0, 1, 2]
+ XCTAssertEqual("[0, 1, 2]", list.description)
+ XCTAssertEqual(3, Python.len(list))
+ XCTAssertEqual("[0, 1, 2]", Python.str(list))
+
+ let polymorphicList = PythonObject(["a", 2, true, 1.5])
+ XCTAssertEqual("a", polymorphicList[0])
+ XCTAssertEqual(2, polymorphicList[1])
+ XCTAssertEqual(true, polymorphicList[2])
+ XCTAssertEqual(1.5, polymorphicList[3])
+ XCTAssertEqual(1.5, polymorphicList[-1])
+
+ XCTAssertEqual(4, polymorphicList.count as Int)
+ XCTAssertEqual(4, polymorphicList.checking.count!)
+ XCTAssertEqual(4, polymorphicList.throwing.count!)
+
+ polymorphicList[2] = 2
+ XCTAssertEqual(2, polymorphicList[2])
+ }
+
+ #if !os(Windows)
+ func testPythonDict() {
+ let dict: PythonObject = ["a": 1, 1: 0.5]
+ XCTAssertEqual(2, Python.len(dict))
+ XCTAssertEqual(1, dict["a"])
+ XCTAssertEqual(0.5, dict[1])
+
+ XCTAssertEqual(2, dict.count as Int)
+ XCTAssertEqual(2, dict.checking.count!)
+ XCTAssertEqual(2, dict.throwing.count!)
+
+ dict["b"] = "c"
+ XCTAssertEqual("c", dict["b"])
+ dict["b"] = "d"
+ XCTAssertEqual("d", dict["b"])
+
+ // Dictionary initializer patch does not work on Python 2, but that
+ // version is no longer being actively supported.
+ guard Python.versionInfo.major >= 3 else {
+ return
+ }
+
+ // Pandas DataFrame regression test spotted in Jupyter. This is
+ // non-deterministic, so repeat it several times to ensure the bug does
+ // not appear.
+ for _ in 0..<15 {
+ let records: [PythonObject] = [
+ ["col 1": 3, "col 2": 5],
+ ["col 1": 8, "col 2": 2]
+ ]
+ let records_description =
+ "[{'col 1': 3, 'col 2': 5}, {'col 1': 8, 'col 2': 2}]"
+ XCTAssertEqual(String(describing: records), records_description)
+
+ let records_alt: [PythonObject] = [
+ ["col 1": 3, "col 2": 5, "col 3": 4],
+ ["col 1": 8, "col 2": 2, "col 3": 4]
+ ]
+ let records_alt_description =
+ "[{'col 1': 3, 'col 2': 5, 'col 3': 4}, {'col 1': 8, 'col 2': 2, 'col 3': 4}]"
+ XCTAssertEqual(String(describing: records_alt), records_alt_description)
+ }
+ }
+
+ func testRange() {
+ let slice = PythonObject(5..<10)
+ XCTAssertEqual(Python.slice(5, 10), slice)
+ XCTAssertEqual(5, slice.start)
+ XCTAssertEqual(10, slice.stop)
+
+ let range = Range(slice)
+ XCTAssertNotNil(range)
+ XCTAssertEqual(5, range?.lowerBound)
+ XCTAssertEqual(10, range?.upperBound)
+
+ XCTAssertNil(Range(PythonObject(5...)))
+ }
+
+ func testPartialRangeFrom() {
+ let slice = PythonObject(5...)
+ XCTAssertEqual(Python.slice(5, Python.None), slice)
+ XCTAssertEqual(5, slice.start)
+
+ let range = PartialRangeFrom(slice)
+ XCTAssertNotNil(range)
+ XCTAssertEqual(5, range?.lowerBound)
+
+ XCTAssertNil(PartialRangeFrom(PythonObject(..<5)))
+ }
+
+ func testPartialRangeUpTo() {
+ let slice = PythonObject(..<5)
+ XCTAssertEqual(Python.slice(5), slice)
+ XCTAssertEqual(5, slice.stop)
- func testRangeIteration() {
- for (index, val) in Python.range(5).enumerated() {
- XCTAssertEqual(PythonObject(index), val)
+ let range = PartialRangeUpTo(slice)
+ XCTAssertNotNil(range)
+ XCTAssertEqual(5, range?.upperBound)
+
+ XCTAssertNil(PartialRangeUpTo(PythonObject(5...)))
+ }
+ #endif
+
+ func testStrideable() {
+ let strideTo = stride(from: PythonObject(0), to: 100, by: 2)
+ XCTAssertEqual(0, strideTo.min()!)
+ XCTAssertEqual(98, strideTo.max()!)
+ XCTAssertEqual([0, 2, 4, 6, 8], Array(strideTo.prefix(5)))
+ XCTAssertEqual([90, 92, 94, 96, 98], Array(strideTo.suffix(5)))
+
+ let strideThrough = stride(from: PythonObject(0), through: 100, by: 2)
+ XCTAssertEqual(0, strideThrough.min()!)
+ XCTAssertEqual(100, strideThrough.max()!)
+ XCTAssertEqual([0, 2, 4, 6, 8], Array(strideThrough.prefix(5)))
+ XCTAssertEqual([92, 94, 96, 98, 100], Array(strideThrough.suffix(5)))
+ }
+
+ func testBinaryOps() {
+ XCTAssertEqual(42, PythonObject(42))
+ XCTAssertEqual(42, PythonObject(2) + PythonObject(40))
+ XCTAssertEqual(2, PythonObject(2) * PythonObject(3) + PythonObject(-4))
+
+ XCTAssertEqual("abcdef", PythonObject("ab") +
+ PythonObject("cde") +
+ PythonObject("") +
+ PythonObject("f"))
+ XCTAssertEqual("ababab", PythonObject("ab") * 3)
+
+ var x = PythonObject(2)
+ x += 3
+ XCTAssertEqual(5, x)
+ x *= 2
+ XCTAssertEqual(10, x)
+ x -= 3
+ XCTAssertEqual(7, x)
+ x /= 2
+ XCTAssertEqual(3.5, x)
+ x += -1
+ XCTAssertEqual(2.5, x)
+ }
+
+ func testUnaryOps() {
+ var x = PythonObject(5)
+ x = -x
+ XCTAssertEqual(-5, x)
+ x = PythonObject(-5)
+ x = -x
+ XCTAssertEqual(5, x)
}
- }
-
- func testErrors() {
- XCTAssertThrowsError(
- try PythonObject(1).__truediv__.throwing.dynamicallyCall(withArguments: 0)
- ) {
- guard let pythonError = $0 as? PythonError else {
- XCTFail("non-Python error: \($0)")
- return
- }
- XCTAssertEqual(pythonError, PythonError.exception("division by zero", traceback: nil))
+
+ func testComparable() {
+ let array: [PythonObject] = [-1, 10, 1, 0, 0]
+ XCTAssertEqual([-1, 0, 0, 1, 10], array.sorted())
+ let list: PythonObject = [-1, 10, 1, 0, 0]
+ XCTAssertEqual([-1, 0, 0, 1, 10], list.sorted())
+ }
+
+ #if !os(Windows)
+ func testHashable() {
+ func compareHashValues(_ x: PythonConvertible) {
+ let a = x.pythonObject
+ let b = x.pythonObject
+ XCTAssertEqual(a.hashValue, b.hashValue)
+ }
+
+ compareHashValues(1)
+ compareHashValues(3.14)
+ compareHashValues("asdf")
+ compareHashValues(PythonObject(tupleOf: 1, 2, 3))
}
- }
-
- func testTuple() {
- let element1: PythonObject = 0
- let element2: PythonObject = "abc"
- let element3: PythonObject = [0, 0]
- let element4: PythonObject = ["a": 0, "b": "c"]
- let pair = PythonObject(tupleOf: element1, element2)
- let (pair1, pair2) = pair.tuple2
- XCTAssertEqual(element1, pair1)
- XCTAssertEqual(element2, pair2)
-
- let triple = PythonObject(tupleOf: element1, element2, element3)
- let (triple1, triple2, triple3) = triple.tuple3
- XCTAssertEqual(element1, triple1)
- XCTAssertEqual(element2, triple2)
- XCTAssertEqual(element3, triple3)
-
- let quadruple = PythonObject(tupleOf: element1, element2, element3, element4)
- let (quadruple1, quadruple2, quadruple3, quadruple4) = quadruple.tuple4
- XCTAssertEqual(element1, quadruple1)
- XCTAssertEqual(element2, quadruple2)
- XCTAssertEqual(element3, quadruple3)
- XCTAssertEqual(element4, quadruple4)
-
- XCTAssertEqual(element2, quadruple[1])
- }
-
- func testMethodCalling() {
- let list: PythonObject = [1, 2]
- list.append(3)
- XCTAssertEqual([1, 2, 3], list)
-
- // Check method binding.
- let append = list.append
- append(4)
- XCTAssertEqual([1, 2, 3, 4], list)
-
- // Check *args/**kwargs behavior: `str.format(*args, **kwargs)`.
- let greeting: PythonObject = "{0} {first} {last}!"
- XCTAssertEqual("Hi John Smith!",
- greeting.format("Hi", first: "John", last: "Smith"))
- XCTAssertEqual("Hey Jane Doe!",
- greeting.format("Hey", first: "Jane", last: "Doe"))
- }
-
- func testConvertibleFromPython() {
- // Ensure that we cover the -1 case as this is used by Python
- // to signal conversion errors.
- let minusOne: PythonObject = -1
- let zero: PythonObject = 0
- let five: PythonObject = 5
- let half: PythonObject = 0.5
- let string: PythonObject = "abc"
-
- XCTAssertEqual(-1, Int(minusOne))
- XCTAssertEqual(-1, Int8(minusOne))
- XCTAssertEqual(-1, Int16(minusOne))
- XCTAssertEqual(-1, Int32(minusOne))
- XCTAssertEqual(-1, Int64(minusOne))
- XCTAssertEqual(-1.0, Float(minusOne))
- XCTAssertEqual(-1.0, Double(minusOne))
-
- XCTAssertEqual(0, Int(zero))
- XCTAssertEqual(0.0, Double(zero))
-
- XCTAssertEqual(5, UInt(five))
- XCTAssertEqual(5, UInt8(five))
- XCTAssertEqual(5, UInt16(five))
- XCTAssertEqual(5, UInt32(five))
- XCTAssertEqual(5, UInt64(five))
- XCTAssertEqual(5.0, Float(five))
- XCTAssertEqual(5.0, Double(five))
-
- XCTAssertEqual(0.5, Float(half))
- XCTAssertEqual(0.5, Double(half))
- // Python rounds down in this case.
- XCTAssertEqual(0, Int(half))
-
- XCTAssertEqual("abc", String(string))
-
- XCTAssertNil(String(zero))
- XCTAssertNil(Int(string))
- XCTAssertNil(Double(string))
- }
-
- func testPythonConvertible() {
- let minusOne: PythonObject = -1
- let five: PythonObject = 5
-
- XCTAssertEqual(minusOne, Int(-1).pythonObject)
- XCTAssertEqual(minusOne, Int8(-1).pythonObject)
- XCTAssertEqual(minusOne, Int16(-1).pythonObject)
- XCTAssertEqual(minusOne, Int32(-1).pythonObject)
- XCTAssertEqual(minusOne, Int64(-1).pythonObject)
- XCTAssertEqual(minusOne, Float(-1).pythonObject)
- XCTAssertEqual(minusOne, Double(-1).pythonObject)
-
- XCTAssertEqual(five, UInt(5).pythonObject)
- XCTAssertEqual(five, UInt8(5).pythonObject)
- XCTAssertEqual(five, UInt16(5).pythonObject)
- XCTAssertEqual(five, UInt32(5).pythonObject)
- XCTAssertEqual(five, UInt64(5).pythonObject)
- XCTAssertEqual(five, Float(5).pythonObject)
- XCTAssertEqual(five, Double(5).pythonObject)
- }
-
- // TODO: https://bugs.swift.org/browse/SR-9230
- // func testSR9230() {
- // XCTAssertEqual(2, Python.len(Python.dict(a: "a", b: "b")))
- // }
-
- // TF-78: isType() consumed refcount for type objects like `PyBool_Type`.
- func testPythonRefCount() {
- let b: PythonObject = true
- for _ in 0...20 {
- // This triggers isType(), which used to crash after repeated invocation
- // because of reduced refcount for `PyBool_Type`.
- _ = Bool.init(b)
+ #endif
+
+ func testRangeIteration() {
+ for (index, val) in Python.range(5).enumerated() {
+ XCTAssertEqual(PythonObject(index), val)
+ }
+ }
+
+ func testErrors() {
+ XCTAssertThrowsError(
+ try PythonObject(1).__truediv__.throwing.dynamicallyCall(withArguments: 0)
+ ) {
+ guard case let PythonError.exception(exception, _) = $0 else {
+ XCTFail("non-Python error: \($0)")
+ return
+ }
+ XCTAssertEqual(exception.__class__.__name__, "ZeroDivisionError")
+ }
+ }
+
+ #if !os(Windows)
+ func testTuple() {
+ let element1: PythonObject = 0
+ let element2: PythonObject = "abc"
+ let element3: PythonObject = [0, 0]
+ let element4: PythonObject = ["a": 0, "b": "c"]
+ let pair = PythonObject(tupleOf: element1, element2)
+ let (pair1, pair2) = pair.tuple2
+ XCTAssertEqual(element1, pair1)
+ XCTAssertEqual(element2, pair2)
+
+ let triple = PythonObject(tupleOf: element1, element2, element3)
+ let (triple1, triple2, triple3) = triple.tuple3
+ XCTAssertEqual(element1, triple1)
+ XCTAssertEqual(element2, triple2)
+ XCTAssertEqual(element3, triple3)
+
+ let quadruple = PythonObject(tupleOf: element1, element2, element3, element4)
+ let (quadruple1, quadruple2, quadruple3, quadruple4) = quadruple.tuple4
+ XCTAssertEqual(element1, quadruple1)
+ XCTAssertEqual(element2, quadruple2)
+ XCTAssertEqual(element3, quadruple3)
+ XCTAssertEqual(element4, quadruple4)
+
+ XCTAssertEqual(element2, quadruple[1])
+ }
+ #endif
+
+ func testMethodCalling() {
+ let list: PythonObject = [1, 2]
+ list.append(3)
+ XCTAssertEqual([1, 2, 3], list)
+
+ // Check method binding.
+ let append = list.append
+ append(4)
+ XCTAssertEqual([1, 2, 3, 4], list)
+
+ // Check *args/**kwargs behavior: `str.format(*args, **kwargs)`.
+ let greeting: PythonObject = "{0} {first} {last}!"
+ XCTAssertEqual("Hi John Smith!",
+ greeting.format("Hi", first: "John", last: "Smith"))
+ XCTAssertEqual("Hey Jane Doe!",
+ greeting.format("Hey", first: "Jane", last: "Doe"))
+ }
+
+ func testConvertibleFromPython() {
+ // Ensure that we cover the -1 case as this is used by Python
+ // to signal conversion errors.
+ let minusOne: PythonObject = -1
+ let zero: PythonObject = 0
+ let five: PythonObject = 5
+ let half: PythonObject = 0.5
+ let string: PythonObject = "abc"
+
+ #if !os(Windows)
+ XCTAssertEqual(-1, Int(minusOne))
+ XCTAssertEqual(-1, Int8(minusOne))
+ XCTAssertEqual(-1, Int16(minusOne))
+ XCTAssertEqual(-1, Int32(minusOne))
+ XCTAssertEqual(-1, Int64(minusOne))
+ #endif
+ XCTAssertEqual(-1.0, Float(minusOne))
+ XCTAssertEqual(-1.0, Double(minusOne))
+
+ XCTAssertEqual(0, Int(zero))
+ XCTAssertEqual(0.0, Double(zero))
+
+ XCTAssertEqual(5, UInt(five))
+ XCTAssertEqual(5, UInt8(five))
+ XCTAssertEqual(5, UInt16(five))
+ XCTAssertEqual(5, UInt32(five))
+ XCTAssertEqual(5, UInt64(five))
+ XCTAssertEqual(5.0, Float(five))
+ XCTAssertEqual(5.0, Double(five))
+
+ XCTAssertEqual(0.5, Float(half))
+ XCTAssertEqual(0.5, Double(half))
+ // Python rounds down in this case.
+ // XCTAssertEqual(0, Int(half))
+
+ XCTAssertEqual("abc", String(string))
+
+ XCTAssertNil(String(zero))
+ #if !os(Windows)
+ XCTAssertNil(Int(string))
+ #endif
+ XCTAssertNil(Double(string))
+ }
+
+ func testPythonConvertible() {
+ let minusOne: PythonObject = -1
+ let five: PythonObject = 5
+
+ XCTAssertEqual(minusOne, Int(-1).pythonObject)
+ XCTAssertEqual(minusOne, Int8(-1).pythonObject)
+ XCTAssertEqual(minusOne, Int16(-1).pythonObject)
+ XCTAssertEqual(minusOne, Int32(-1).pythonObject)
+ XCTAssertEqual(minusOne, Int64(-1).pythonObject)
+ XCTAssertEqual(minusOne, Float(-1).pythonObject)
+ XCTAssertEqual(minusOne, Double(-1).pythonObject)
+
+ XCTAssertEqual(five, UInt(5).pythonObject)
+ XCTAssertEqual(five, UInt8(5).pythonObject)
+ XCTAssertEqual(five, UInt16(5).pythonObject)
+ XCTAssertEqual(five, UInt32(5).pythonObject)
+ XCTAssertEqual(five, UInt64(5).pythonObject)
+ XCTAssertEqual(five, Float(5).pythonObject)
+ XCTAssertEqual(five, Double(5).pythonObject)
+ }
+
+ // SR-9230: https://bugs.swift.org/browse/SR-9230
+ func testSR9230() {
+ let pythonDict = Python.dict(a: "a", b: "b")
+ XCTAssertEqual(Python.len(pythonDict), 2)
+ }
+
+ // TF-78: isType() consumed refcount for type objects like `PyBool_Type`.
+ func testPythonRefCount() {
+ let b: PythonObject = true
+ for _ in 0...20 {
+ // This triggers isType(), which used to crash after repeated invocation
+ // because of reduced refcount for `PyBool_Type`.
+ _ = Bool.init(b)
+ }
+ }
+
+ func testPythonBytes() {
+ let bytes = PythonBytes([UInt8(1), UInt8(2), UInt8(3), UInt8(4)])
+ bytes.withUnsafeBytes {
+ XCTAssertEqual(Array($0), [1, 2, 3, 4])
+ }
+ }
+
+ func testPythonBytesInt8() {
+ let bytes = PythonBytes([Int8(1), Int8(2), Int8(3), Int8(4)])
+ bytes.withUnsafeBytes {
+ XCTAssertEqual(Array($0), [1, 2, 3, 4])
+ }
+ }
+
+ func testPythonBytesNonContiguousSequence() {
+ let bytes = PythonBytes(CollectionOfOne(UInt8(1)))
+ bytes.withUnsafeBytes {
+ XCTAssertEqual(Array($0), [1])
+ }
+ }
+
+ func testPythonBytesNonContiguousSequenceInt8() {
+ let bytes = PythonBytes(CollectionOfOne(Int8(1)))
+ bytes.withUnsafeBytes {
+ XCTAssertEqual(Array($0), [1])
+ }
+ }
+
+ func testBytesConversion() {
+ let bytes = PythonBytes(CollectionOfOne(UInt8(1)))
+ let otherBytes = PythonBytes(bytes.pythonObject)
+ otherBytes?.withUnsafeBytes {
+ XCTAssertEqual(Array($0), [1])
+ }
+ XCTAssertEqual(bytes, otherBytes)
+ }
+
+ /// Tests an emulation of the Python `with` statement.
+ ///
+ /// Mirrors:
+ /// ```python
+ /// with open('temp', 'w') as opened_file:
+ /// opened_file.write('Contents')
+ /// with open('temp', 'r') as opened_file:
+ /// contents = opened_file.read()
+ /// ```
+ func testWith() {
+ Python.with(Python.open("temp", "w")) { opened_file in
+ opened_file.write("Contents")
+ }
+ Python.with(Python.open("temp", "r")) { opened_file in
+ let contents = opened_file.read()
+ XCTAssertEqual("Contents", String(contents)!)
+ }
}
- }
}