//===-- Python.swift ------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines an interoperability layer for talking to Python from Swift.
//
//===----------------------------------------------------------------------===//
//
// The model provided by this file is completely dynamic and does not require
// invasive compiler support.
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// `PyReference` definition
//===----------------------------------------------------------------------===//
/// 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`.
///
/// A `PyReference` instance has ownership of its underlying `PyObject`, which
/// must be non-null.
///
// - Note: When Swift has ownership, `PyReference` should be removed.
// `PythonObject` will define copy constructors, move constructors, etc. to
// 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)
}
// This `PyReference` adopts the +1 reference and will decrement it in the
// future.
init(consuming pointer: PyObjectPointer) {
self.pointer = pointer
}
deinit {
Py_DecRef(pointer)
}
var borrowedPyObject: PyObjectPointer {
return pointer
}
var ownedPyObject: OwnedPyObjectPointer {
Py_IncRef(pointer)
return pointer
}
}
//===----------------------------------------------------------------------===//
// `PythonObject` definition
//===----------------------------------------------------------------------===//
// - Note: When Swift has ownership, `PythonObject` will define copy
// constructors, move constructors, etc. to implement move semantics.
/// `PythonObject` represents an object in Python and supports dynamic member
/// lookup. Any member access like `object.foo` will dynamically request the
/// Python runtime for a member with the specified name in this object.
///
/// `PythonObject` is passed to and returned from all Python function calls and
/// member references. It supports standard Python arithmetic and comparison
/// operators.
///
/// Internally, `PythonObject` is implemented as a reference-counted pointer to
/// a Python C API `PyObject`.
@dynamicCallable
@dynamicMemberLookup
public struct PythonObject {
/// The underlying `PyReference`.
fileprivate var reference: PyReference
@usableFromInline
init(_ pointer: PyReference) {
reference = 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)
}
fileprivate var borrowedPyObject: PyObjectPointer {
return reference.borrowedPyObject
}
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))!
}
}
// Make `PythonObject` show up nicely in the Xcode Playground results sidebar.
extension PythonObject : CustomPlaygroundDisplayConvertible {
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)
}
}
//===----------------------------------------------------------------------===//
// `PythonConvertible` protocol
//===----------------------------------------------------------------------===//
/// A type whose values can be converted to a `PythonObject`.
public protocol PythonConvertible {
/// 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)
}
}
/// 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 ownedPyObject: OwnedPyObjectPointer {
return pythonObject.ownedPyObject
}
}
//===----------------------------------------------------------------------===//
// `ConvertibleFromPython` protocol
//===----------------------------------------------------------------------===//
/// 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)
}
// `PythonObject` is trivially `PythonConvertible`.
extension PythonObject : PythonConvertible, ConvertibleFromPython {
public init(_ object: PythonObject) {
self.init(consuming: object.ownedPyObject)
}
public var pythonObject: PythonObject { return self }
}
//===----------------------------------------------------------------------===//
// `PythonObject` callable implementation
//===----------------------------------------------------------------------===//
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)
}
}
/// 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)
}
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)"
}
}
}
// 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 }
var type: PyObjectPointer?
var value: PyObjectPointer?
var traceback: PyObjectPointer?
// 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)
}
/// A `PythonObject` wrapper that enables throwing method calls.
/// Exceptions produced by Python functions are reflected as Swift errors and
/// thrown.
/// - Note: It is intentional that `ThrowingPythonObject` does not have the
/// `@dynamicCallable` attribute because the call syntax is unintuitive:
/// `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 {
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
}
}
//===----------------------------------------------------------------------===//
// `PythonObject` member access implementation
//===----------------------------------------------------------------------===//
public extension PythonObject {
/// Returns a `PythonObject` wrapper capable of member accesses.
var checking: CheckingPythonObject {
return CheckingPythonObject(self)
}
}
/// A `PythonObject` wrapper that enables member accesses.
/// Member access operations return an `Optional` result. When member access
/// 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)
}
public var count: Int? {
Int(Python.len(base))
}
}
//===----------------------------------------------------------------------===//
// Core `PythonObject` API
//===----------------------------------------------------------------------===//
/// 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 })
}
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)
}
/// 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)
}
}
//===----------------------------------------------------------------------===//
// Python interface implementation
//===----------------------------------------------------------------------===//
/// The global Python interface.
///
/// You can import Python modules and access Python builtin types and functions
/// via the `Python` global variable.
///
/// import Python
/// // Import modules.
/// let os = Python.import("os")
/// let np = Python.import("numpy")
///
/// // Use builtin types and functions.
/// let list: PythonObject = [1, 2, 3]
/// print(Python.len(list)) // Prints 3.
/// print(Python.type(list) == Python.list) // Prints true.
@_fixed_layout
public let Python = PythonInterface()
/// An interface for Python.
///
/// `PythonInterface` allows interaction with Python. It can be used to import
/// modules and dynamically access Python builtin types and functions.
/// - Note: It is not intended for `PythonInterface` to be initialized
/// directly. Instead, please use the global instance of `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:
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__()
}
}
//===----------------------------------------------------------------------===//
// Helpers for Python tuple types
//===----------------------------------------------------------------------===//
// 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
}
public extension PythonObject {
// Tuples require explicit support because tuple types cannot conform to
// protocols.
init(tupleOf elements: PythonConvertible...) {
self.init(tupleContentsOf: elements)
}
init(tupleContentsOf elements: T)
where T.Element == PythonConvertible {
self.init(consuming: pyTuple(elements.map { $0.pythonObject }))
}
init(tupleContentsOf elements: T)
where T.Element : PythonConvertible {
self.init(consuming: pyTuple(elements))
}
}
//===----------------------------------------------------------------------===//
// `PythonConvertible` conformance for basic Swift types
//===----------------------------------------------------------------------===//
/// Return true if the specified objects an instance of the low-level Python
/// type descriptor passed in as 'type'.
private func isType(_ object: PythonObject,
type: PyObjectPointer) -> Bool {
let typePyRef = PythonObject(type)
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) }
// 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 }
let pyObject = pythonObject.ownedPyObject
defer { Py_DecRef(pyObject) }
self = pyObject == _Py_TrueStruct
}
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
}
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)!
}
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")
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
}
self = value
}
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
}
self = value
}
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
}
self = value
}
public var pythonObject: PythonObject {
_ = Python // Ensure Python is initialized.
return PythonObject(consuming: PyFloat_FromDouble(self))
}
}
//===----------------------------------------------------------------------===//
// `PythonConvertible` conformances for `FixedWidthInteger` and `Float`
//===----------------------------------------------------------------------===//
// Any `FixedWidthInteger` type is `PythonConvertible` via the `Int`/`UInt`
// implementation.
extension Int8 : PythonConvertible, ConvertibleFromPython {
public init?(_ pythonObject: PythonObject) {
guard let i = Int(pythonObject) else { return nil }
self.init(i)
}
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 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 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 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 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 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 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 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 var pythonObject: PythonObject {
return Double(self).pythonObject
}
}
//===----------------------------------------------------------------------===//
// `PythonConvertible` conformance for `Optional`
//===----------------------------------------------------------------------===//
extension Optional : PythonConvertible where Wrapped : PythonConvertible {
public var pythonObject: PythonObject {
return self?.pythonObject ?? Python.None
}
}
//===----------------------------------------------------------------------===//
// `ConvertibleFromPython` conformance for `Optional`
//===----------------------------------------------------------------------===//
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)
}
}
}
//===----------------------------------------------------------------------===//
// `PythonConvertible` and `ConvertibleFromPython conformance for
// `Array` and `Dictionary`
//===----------------------------------------------------------------------===//
// `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)
}
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)
}
}
}
// `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)
}
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
}
}
}
}
//===----------------------------------------------------------------------===//
// `PythonConvertible` and `ConvertibleFromPython` conformances
// for `Range` types
//===----------------------------------------------------------------------===//
extension Range : PythonConvertible where Bound : PythonConvertible {
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
}
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)
}
}
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
}
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)
}
}
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
}
self.init(upperBound)
}
}
//===----------------------------------------------------------------------===//
// Standard operators and conformances
//===----------------------------------------------------------------------===//
private typealias PythonBinaryOp =
(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!)
}
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_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: 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: 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_InPlaceAnd, 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_InPlaceXor, 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 typealias Magnitude = PythonObject
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 func distance(to other: PythonObject) -> Stride {
return other - self
}
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)
}
}
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)")
}
hasher.combine(hash)
}
}
extension PythonObject : MutableCollection {
public typealias Index = PythonObject
public typealias Element = PythonObject
public var startIndex: Index {
return 0
}
public var endIndex: Index {
return Python.len(self)
}
public func index(after i: Index) -> Index {
return i + PythonObject(1)
}
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 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))
}
}
extension PythonObject {
public var count: Int {
checking.count!
}
}
//===----------------------------------------------------------------------===//
// `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
// 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
}
}