From a0ea9dd0890ac1177915cd28dec396142309c7e7 Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 26 Aug 2024 13:38:29 -0400 Subject: [PATCH 01/29] Remove EOL whitespace. (#63) --- PythonKit/NumpyConversion.swift | 6 +- PythonKit/Python.swift | 278 +++++++++--------- PythonKit/PythonLibrary.swift | 58 ++-- .../PythonKitTests/NumpyConversionTests.swift | 14 +- .../PythonKitTests/PythonFunctionTests.swift | 116 ++++---- Tests/PythonKitTests/PythonRuntimeTests.swift | 92 +++--- 6 files changed, 282 insertions(+), 282 deletions(-) diff --git a/PythonKit/NumpyConversion.swift b/PythonKit/NumpyConversion.swift index fc73a33..c802694 100644 --- a/PythonKit/NumpyConversion.swift +++ b/PythonKit/NumpyConversion.swift @@ -110,18 +110,18 @@ where Element : NumpyScalarCompatible { 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 diff --git a/PythonKit/Python.swift b/PythonKit/Python.swift index 841a147..35d2fcb 100644 --- a/PythonKit/Python.swift +++ b/PythonKit/Python.swift @@ -39,7 +39,7 @@ typealias OwnedPyObjectPointer = PyObjectPointer @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. @@ -47,21 +47,21 @@ final class PyReference { 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 @@ -90,26 +90,26 @@ final class PyReference { 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 } @@ -165,7 +165,7 @@ fileprivate extension PythonConvertible { var borrowedPyObject: PyObjectPointer { return pythonObject.borrowedPyObject } - + var ownedPyObject: OwnedPyObjectPointer { return pythonObject.ownedPyObject } @@ -189,7 +189,7 @@ extension PythonObject : PythonConvertible, ConvertibleFromPython { public init(_ object: PythonObject) { self.init(consuming: object.ownedPyObject) } - + public var pythonObject: PythonObject { return self } } @@ -210,7 +210,7 @@ public extension PythonObject { 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. @@ -218,7 +218,7 @@ public enum PythonError : Error, Equatable { /// object. /// - An invalid keyword argument was specified. case invalidCall(PythonObject) - + /// A module import error. case invalidModule(String) } @@ -248,14 +248,14 @@ extension PythonError : CustomStringConvertible { // 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) } @@ -271,11 +271,11 @@ private func throwPythonErrorIfPresent() throws { /// `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. @@ -285,7 +285,7 @@ public struct ThrowingPythonObject { 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. @@ -294,18 +294,18 @@ public struct ThrowingPythonObject { 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() @@ -313,7 +313,7 @@ public struct ThrowingPythonObject { } 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. @@ -324,7 +324,7 @@ public struct ThrowingPythonObject { 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 @@ -333,17 +333,17 @@ public struct ThrowingPythonObject { [(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) @@ -360,20 +360,20 @@ public struct ThrowingPythonObject { 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() @@ -381,7 +381,7 @@ public struct ThrowingPythonObject { } return PythonObject(consuming: result) } - + /// Converts to a 2-tuple, if possible. public var tuple2: (PythonObject, PythonObject)? { let ct = base.checking @@ -390,7 +390,7 @@ public struct ThrowingPythonObject { } return (elt0, elt1) } - + /// Converts to a 3-tuple, if possible. public var tuple3: (PythonObject, PythonObject, PythonObject)? { let ct = base.checking @@ -399,7 +399,7 @@ public struct ThrowingPythonObject { } return (elt0, elt1, elt2) } - + /// Converts to a 4-tuple, if possible. public var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject)? { let ct = base.checking @@ -409,7 +409,7 @@ public struct ThrowingPythonObject { } return (elt0, elt1, elt2, elt3) } - + public var count: Int? { base.checking.count } @@ -434,11 +434,11 @@ public extension PythonObject { 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 @@ -451,7 +451,7 @@ public struct CheckingPythonObject { 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. @@ -463,7 +463,7 @@ public struct CheckingPythonObject { Py_DecRef(keyObject) Py_DecRef(selfObject) } - + // `PyObject_GetItem` returns +1 reference. if let result = PyObject_GetItem(selfObject, keyObject) { return PythonObject(consuming: result) @@ -478,7 +478,7 @@ public struct CheckingPythonObject { Py_DecRef(keyObject) Py_DecRef(selfObject) } - + if let newValue = newValue { let newValueObject = newValue.ownedPyObject PyObject_SetItem(selfObject, keyObject, newValueObject) @@ -489,7 +489,7 @@ public struct CheckingPythonObject { } } } - + /// Access the element corresponding to the specified `PythonConvertible` /// values representing a key. /// - Note: This is equivalent to `object[key]` in Python. @@ -501,7 +501,7 @@ public struct CheckingPythonObject { 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 { @@ -509,7 +509,7 @@ public struct CheckingPythonObject { } 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 { @@ -517,7 +517,7 @@ public struct CheckingPythonObject { } 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], @@ -526,7 +526,7 @@ public struct CheckingPythonObject { } return (elt0, elt1, elt2, elt3) } - + public var count: Int? { Int(Python.len(base)) } @@ -559,7 +559,7 @@ public extension PythonObject { defer { Py_DecRef(selfObject) } let valueObject = newValue.ownedPyObject defer { Py_DecRef(valueObject) } - + if PyObject_SetAttrString(selfObject, memberName, valueObject) == -1 { try! throwPythonErrorIfPresent() fatalError(""" @@ -569,7 +569,7 @@ public extension PythonObject { } } } - + /// Access the element corresponding to the specified `PythonConvertible` /// values representing a key. /// - Note: This is equivalent to `object[key]` in Python. @@ -587,7 +587,7 @@ public extension PythonObject { checking[key] = newValue } } - + /// Converts to a 2-tuple. var tuple2: (PythonObject, PythonObject) { guard let result = checking.tuple2 else { @@ -595,7 +595,7 @@ public extension PythonObject { } return result } - + /// Converts to a 3-tuple. var tuple3: (PythonObject, PythonObject, PythonObject) { guard let result = checking.tuple3 else { @@ -603,7 +603,7 @@ public extension PythonObject { } return result } - + /// Converts to a 4-tuple. var tuple4: (PythonObject, PythonObject, PythonObject, PythonObject) { guard let result = checking.tuple4 else { @@ -611,7 +611,7 @@ public extension PythonObject { } return result } - + /// Call `self` with the specified positional arguments. /// - Precondition: `self` must be a Python callable. /// - Parameter args: Positional arguments for the Python callable. @@ -620,7 +620,7 @@ public extension PythonObject { 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. @@ -630,7 +630,7 @@ public extension PythonObject { 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 @@ -673,16 +673,16 @@ public let Python = PythonInterface() 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 = [""] @@ -694,7 +694,7 @@ public struct PythonInterface { 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() @@ -702,27 +702,27 @@ public struct PythonInterface { } 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__()`. @@ -754,12 +754,12 @@ public extension PythonObject { 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)) @@ -775,14 +775,14 @@ public extension PythonObject { 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 } @@ -790,13 +790,13 @@ private func isType(_ object: PythonObject, 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)) @@ -807,14 +807,14 @@ 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 { @@ -834,17 +834,17 @@ fileprivate extension PythonObject { ) -> 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 - + } } @@ -858,7 +858,7 @@ extension Int : PythonConvertible, ConvertibleFromPython { } self = value } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyInt_FromLong(self)) @@ -876,7 +876,7 @@ extension UInt : PythonConvertible, ConvertibleFromPython { } self = value } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyInt_FromSize_t(self)) @@ -893,7 +893,7 @@ extension Double : PythonConvertible, ConvertibleFromPython { } self = value } - + public var pythonObject: PythonObject { _ = Python // Ensure Python is initialized. return PythonObject(consuming: PyFloat_FromDouble(self)) @@ -912,7 +912,7 @@ extension Int8 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -923,7 +923,7 @@ extension Int16 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -934,7 +934,7 @@ extension Int32 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -945,7 +945,7 @@ extension Int64 : PythonConvertible, ConvertibleFromPython { guard let i = Int(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return Int(self).pythonObject } @@ -956,7 +956,7 @@ extension UInt8 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -967,7 +967,7 @@ extension UInt16 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -978,7 +978,7 @@ extension UInt32 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -989,7 +989,7 @@ extension UInt64 : PythonConvertible, ConvertibleFromPython { guard let i = UInt(pythonObject) else { return nil } self.init(i) } - + public var pythonObject: PythonObject { return UInt(self).pythonObject } @@ -1002,7 +1002,7 @@ extension Float : PythonConvertible, ConvertibleFromPython { guard let v = Double(pythonObject) else { return nil } self.init(v) } - + public var pythonObject: PythonObject { return Double(self).pythonObject } @@ -1087,12 +1087,12 @@ 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 { @@ -1204,7 +1204,7 @@ 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) } @@ -1212,23 +1212,23 @@ public extension PythonObject { 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) } @@ -1268,9 +1268,9 @@ 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 } @@ -1284,11 +1284,11 @@ extension PythonObject : SignedNumeric { 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 } @@ -1318,7 +1318,7 @@ extension PythonObject : Equatable, Comparable { 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) } @@ -1330,11 +1330,11 @@ extension PythonObject : Equatable, Comparable { 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) } @@ -1357,27 +1357,27 @@ public extension PythonObject { } 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) } @@ -1395,19 +1395,19 @@ extension PythonObject : Hashable { 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] @@ -1421,7 +1421,7 @@ extension PythonObject : MutableCollection { 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() @@ -1430,7 +1430,7 @@ extension PythonObject : Sequence { return PythonObject(consuming: result) } } - + public func makeIterator() -> Iterator { guard let result = PyObject_GetIter(borrowedPyObject) else { try! throwPythonErrorIfPresent() @@ -1610,47 +1610,47 @@ final class PyFunction { 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) @@ -1661,21 +1661,21 @@ final class PyFunction { 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 @@ -1751,7 +1751,7 @@ fileprivate extension PythonFunction { 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. @@ -1793,7 +1793,7 @@ fileprivate extension PythonFunction { 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 @@ -1864,16 +1864,16 @@ struct PyMethodDef { 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) } @@ -1893,18 +1893,18 @@ extension PythonInstanceMethod : PythonConvertible { 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") }) @@ -1914,14 +1914,14 @@ public struct PythonClass { 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) } diff --git a/PythonKit/PythonLibrary.swift b/PythonKit/PythonLibrary.swift index 7befe64..6149478 100644 --- a/PythonKit/PythonLibrary.swift +++ b/PythonKit/PythonLibrary.swift @@ -32,7 +32,7 @@ import WinSDK public struct PythonLibrary { public enum Error: Swift.Error, Equatable, CustomStringConvertible { case pythonLibraryNotFound - + public var description: String { switch self { case .pythonLibraryNotFound: @@ -43,23 +43,23 @@ public struct PythonLibrary { } } } - + 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 } - + /// 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 } @@ -70,7 +70,7 @@ public struct PythonLibrary { self.isPythonLibraryLoaded = true self._pythonLibraryHandle = pythonLibraryHandle } - + private static let isLegacyPython: Bool = { let isLegacyPython = PythonLibrary.loadSymbol(PythonLibrary.pythonLibraryHandle, PythonLibrary.pythonLegacySymbolName) != nil if isLegacyPython { @@ -78,14 +78,14 @@ public struct PythonLibrary { } 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 } - + log("Loading symbol '\(name)' from the Python library...") return unsafeBitCast(self.loadSymbol(self.pythonLibraryHandle, name), to: type) } @@ -95,9 +95,9 @@ public struct PythonLibrary { extension PythonLibrary { private static let supportedMajorVersions: [Int] = [3, 2] private static let supportedMinorVersions: [Int] = Array(0...30).reversed() - + private static let libraryPathVersionCharacter: Character = ":" - + #if canImport(Darwin) private static var libraryNames = ["Python.framework/Versions/:/Python"] private static var libraryPathExtensions = [""] @@ -114,7 +114,7 @@ extension PythonLibrary { private static var librarySearchPaths = [""] private static var libraryVersionSeparator = "" #endif - + private static let libraryPaths: [String] = { var libraryPaths: [String] = [] for librarySearchPath in librarySearchPaths { @@ -128,7 +128,7 @@ extension PythonLibrary { } return libraryPaths }() - + private static func loadSymbol( _ libraryHandle: UnsafeMutableRawPointer?, _ name: String) -> UnsafeMutableRawPointer? { #if os(Windows) @@ -141,12 +141,12 @@ extension PythonLibrary { return dlsym(libraryHandle, name) #endif } - + private static func isPythonLibraryLoaded(at pythonLibraryHandle: UnsafeMutableRawPointer? = nil) -> Bool { let pythonLibraryHandle = pythonLibraryHandle ?? self.defaultLibraryHandle return self.loadSymbol(pythonLibraryHandle, self.pythonInitializeSymbolName) != nil } - + private static func loadPythonLibrary() -> UnsafeMutableRawPointer? { let pythonLibraryHandle: UnsafeMutableRawPointer? if self.isPythonLibraryLoaded() { @@ -160,7 +160,7 @@ extension PythonLibrary { } return pythonLibraryHandle } - + private static func findAndLoadExternalPythonLibrary() -> UnsafeMutableRawPointer? { for majorVersion in supportedMajorVersions { for minorVersion in supportedMinorVersions { @@ -176,11 +176,11 @@ extension PythonLibrary { } return nil } - + 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, @@ -188,7 +188,7 @@ extension PythonLibrary { return nil } } - + let libraryVersionString = versionString .split(separator: PythonVersion.versionSeparator) .joined(separator: libraryVersionSeparator) @@ -196,7 +196,7 @@ extension PythonLibrary { .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) @@ -206,7 +206,7 @@ extension PythonLibrary { // modules may depend on this .so file. let pythonLibraryHandle = dlopen(path, RTLD_LAZY | RTLD_GLOBAL) #endif - + if pythonLibraryHandle != nil { self.log("Library at '\(path)' was successfully loaded.") } @@ -222,7 +222,7 @@ extension PythonLibrary { has already been loaded. """) } - + /// Use the Python library with the specified version. /// - Parameters: /// - major: Major version or nil to use any Python version. @@ -232,7 +232,7 @@ extension PythonLibrary { let version = PythonVersion(major: major, minor: minor) PythonLibrary.Environment.version.set(version.versionString) } - + /// 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?) { @@ -246,9 +246,9 @@ 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 \ @@ -257,7 +257,7 @@ extension PythonLibrary { self.major = major self.minor = minor } - + var versionString: String { guard let major = major else { return "" } var versionString = String(major) @@ -274,22 +274,22 @@ 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" - + 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) diff --git a/Tests/PythonKitTests/NumpyConversionTests.swift b/Tests/PythonKitTests/NumpyConversionTests.swift index 7f47946..4189470 100644 --- a/Tests/PythonKitTests/NumpyConversionTests.swift +++ b/Tests/PythonKitTests/NumpyConversionTests.swift @@ -3,25 +3,25 @@ import PythonKit class NumpyConversionTests: XCTestCase { static var numpyModule = try? Python.attemptImport("numpy") - + func testArrayConversion() { guard let np = NumpyConversionTests.numpyModule else { return } - + 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 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 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 diff --git a/Tests/PythonKitTests/PythonFunctionTests.swift b/Tests/PythonKitTests/PythonFunctionTests.swift index 7be1afa..db39d06 100644 --- a/Tests/PythonKitTests/PythonFunctionTests.swift +++ b/Tests/PythonKitTests/PythonFunctionTests.swift @@ -7,24 +7,24 @@ class PythonFunctionTests: XCTestCase { 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. @@ -36,76 +36,76 @@ class PythonFunctionTests: XCTestCase { 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 @@ -114,7 +114,7 @@ class PythonFunctionTests: XCTestCase { guard canUsePythonFunction else { return } - + let MyClass = PythonClass( "MyClass", superclasses: [Python.object], @@ -122,76 +122,76 @@ class PythonFunctionTests: XCTestCase { "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") @@ -199,15 +199,15 @@ class PythonFunctionTests: XCTestCase { 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 testFunction = PythonFunction { _ in throw HelloWorldException("EXAMPLE ERROR MESSAGE", 2) }.pythonObject - + /* do { try testFunction.throwing.dynamicallyCall(withArguments: []) @@ -217,7 +217,7 @@ class PythonFunctionTests: XCTestCase { XCTFail("A string could not be created from a HelloWorldException.") return } - + XCTAssertTrue(description.contains("EXAMPLE ERROR MESSAGE")) XCTAssertTrue(description.contains("HelloWorldException")) } catch { @@ -225,19 +225,19 @@ class PythonFunctionTests: XCTestCase { } */ } - + // 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 @@ -247,7 +247,7 @@ class PythonFunctionTests: XCTestCase { // test_method(): // - param1 // - param2 - + let BaseClass = PythonClass( "BaseClass", superclasses: [], @@ -259,7 +259,7 @@ class PythonFunctionTests: XCTestCase { `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) @@ -268,7 +268,7 @@ class PythonFunctionTests: XCTestCase { } ] ).pythonObject - + // Derived class accepts the following arguments: // __init__(): // - param2 @@ -278,7 +278,7 @@ class PythonFunctionTests: XCTestCase { // - param1 // - param2 // - param3 - + let DerivedClass = PythonClass( "DerivedClass", superclasses: [], @@ -286,7 +286,7 @@ class PythonFunctionTests: XCTestCase { "__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). @@ -296,44 +296,44 @@ class PythonFunctionTests: XCTestCase { ("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) diff --git a/Tests/PythonKitTests/PythonRuntimeTests.swift b/Tests/PythonKitTests/PythonRuntimeTests.swift index 5ee4381..90f1418 100644 --- a/Tests/PythonKitTests/PythonRuntimeTests.swift +++ b/Tests/PythonKitTests/PythonRuntimeTests.swift @@ -6,39 +6,39 @@ class PythonRuntimeTests: XCTestCase { 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]) - + 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" @@ -61,7 +61,7 @@ class PythonRuntimeTests: XCTestCase { 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] @@ -71,71 +71,71 @@ class PythonRuntimeTests: XCTestCase { 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) - + 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) @@ -164,7 +164,7 @@ class PythonRuntimeTests: XCTestCase { 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) { @@ -172,20 +172,20 @@ class PythonRuntimeTests: XCTestCase { let b = x.pythonObject XCTAssertEqual(a.hashValue, b.hashValue) } - + compareHashValues(1) compareHashValues(3.14) compareHashValues("asdf") compareHashValues(PythonObject(tupleOf: 1, 2, 3)) } #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) @@ -197,7 +197,7 @@ class PythonRuntimeTests: XCTestCase { XCTAssertEqual(exception.__class__.__name__, "ZeroDivisionError") } } - + #if !os(Windows) func testTuple() { let element1: PythonObject = 0 @@ -208,34 +208,34 @@ class PythonRuntimeTests: XCTestCase { 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!", @@ -243,7 +243,7 @@ class PythonRuntimeTests: XCTestCase { 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. @@ -252,7 +252,7 @@ class PythonRuntimeTests: XCTestCase { let five: PythonObject = 5 let half: PythonObject = 0.5 let string: PythonObject = "abc" - + #if !os(Windows) XCTAssertEqual(-1, Int(minusOne)) XCTAssertEqual(-1, Int8(minusOne)) @@ -262,10 +262,10 @@ class PythonRuntimeTests: XCTestCase { #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)) @@ -273,25 +273,25 @@ class PythonRuntimeTests: XCTestCase { 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) @@ -299,7 +299,7 @@ class PythonRuntimeTests: XCTestCase { 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) @@ -308,13 +308,13 @@ class PythonRuntimeTests: XCTestCase { 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 @@ -361,7 +361,7 @@ class PythonRuntimeTests: XCTestCase { } XCTAssertEqual(bytes, otherBytes) } - + /// Tests an emulation of the Python `with` statement. /// /// Mirrors: From 11509ad0cdc1becdb11b325a8fdff606793c06a3 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 29 Aug 2024 13:43:59 -0400 Subject: [PATCH 02/29] Add temp to git ignore. (#66) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9d88ee1..3d50865 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # Temporary Items *.tmp *.tmp.* +temp # Virtual Environments /venv*/ From 6fee7617cfa910fbac7035276e295ba967adbbb4 Mon Sep 17 00:00:00 2001 From: Jeffrey Davis Date: Tue, 27 Aug 2024 19:28:41 -0700 Subject: [PATCH 03/29] Fix warnings. --- PythonKit/NumpyConversion.swift | 2 +- Tests/PythonKitTests/PythonFunctionTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/PythonKit/NumpyConversion.swift b/PythonKit/NumpyConversion.swift index c802694..e0c81a2 100644 --- a/PythonKit/NumpyConversion.swift +++ b/PythonKit/NumpyConversion.swift @@ -137,7 +137,7 @@ where Element : NumpyScalarCompatible { self.init(repeating: dummyPointer.move(), count: scalarCount) dummyPointer.deallocate() withUnsafeMutableBufferPointer { buffPtr in - buffPtr.baseAddress!.assign(from: ptr, count: scalarCount) + buffPtr.baseAddress!.update(from: ptr, count: scalarCount) } } } diff --git a/Tests/PythonKitTests/PythonFunctionTests.swift b/Tests/PythonKitTests/PythonFunctionTests.swift index db39d06..f11ad57 100644 --- a/Tests/PythonKitTests/PythonFunctionTests.swift +++ b/Tests/PythonKitTests/PythonFunctionTests.swift @@ -204,7 +204,7 @@ class PythonFunctionTests: XCTestCase { // Example of function with no named parameters, which can be stated // ergonomically using an underscore. The ignored input is a [PythonObject]. - let testFunction = PythonFunction { _ in + let _ = PythonFunction { _ in throw HelloWorldException("EXAMPLE ERROR MESSAGE", 2) }.pythonObject From d5ba1b3b4834e8c47e0cdf0989f458bc59ab0b45 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 4 Apr 2025 14:38:07 -0700 Subject: [PATCH 04/29] Use dlerror() when dlopen() fails. --- PythonKit/PythonLibrary.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/PythonKit/PythonLibrary.swift b/PythonKit/PythonLibrary.swift index 6149478..ab94a3f 100644 --- a/PythonKit/PythonLibrary.swift +++ b/PythonKit/PythonLibrary.swift @@ -205,6 +205,13 @@ extension PythonLibrary { // 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 { From 04205cdda392c154b1b5837978497c589398dc9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 11 Sep 2025 12:27:42 +0200 Subject: [PATCH 05/29] Updated project [SKIP-CI] --- AGENTS.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..78ad2c6 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,58 @@ +# Agents + +This file provides critical development and deployment guidelines for AI agents working with this app project. + +## Development + +### Project Structure +- App-specific logic belongs in framework targets. +- **External SwiftPM dependencies** contain shared logic and helper functions. +- UI code should be separated from business logic. +- For app development focus on the **Xcode project**. Package.swift typically supports building CLI tools only. + +### Code Patterns +- Use appropriate logging frameworks instead of print statements. +- Follow protocol-oriented design patterns. +- **CRITICAL: Always check FoundationKit, LoggerKit, SwiftUIKit, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication. + +#### Core Frameworks Extensions Examples +- **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer +- **NSAppleScript**: `execute()` method with proper Swift error handling +- **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()` +- **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening +- **Process**: Enhanced execution utilities with output capture +- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app preferences + +### Working with External SwiftPM Dependencies +- External dependencies are separate SwiftPM repositories shared across multiple apps. +- Changes to core models, utilities, business rules must be made in their respective packages (typically you can find them in the project parent directory). +- To build or test a SwiftPM project use `DeveloperBuildTool` instead of `swift build`. +- Workflow: Edit external package → Build → Commit & Push → Return to main project → Reload package dependencies → Rebuild. + +### Verification Requirements +- **Always verify changes work** by building or testing before considering task complete. +- Build or test external dependencies if changes were made to them. +- Build main project to ensure all changes integrate properly. +- Run the application when possible to ensure functionality works as expected. Make sure to terminate any running instances of the app before running again. +- Changes are not complete until successfully built and verified. + +### Development Workflow +- All development work is done from the `master` branch. +- By default, feature development, bug fixes, and general improvements happen directly on `master`. +- Optionally, `feature/` or `fix/` branches can be created for parallel work. + +## Deployment + +### Release Process +For app releases: +1. **Version Update**: Update the Xcode project Marketing Version setting and / or the `CFBundleShortVersionString` in all relevant Info.plist files. Use the standard versioning format (eg. `1.0`, then `1.1` or `1.0.1` for minor updates). Ensure the version is consistent across all targets (app, extensions, frameworks). +2. **Commit Changes**: Add, commit, and push all pending changes to `master` branch. +3. **Merge to Release**: Switch to `release` branch, merge from `master`, and push to remote. Note: Only the `release` branch is linked to Xcode Cloud for automated builds. +4. **Tag Release**: After pushing to `release` branch, you MUST ALWAYS create and push a git tag. Use the `v1.0` or `v1.0.1` format for tags and a message like `ProjectName v1.0.1`. Create or replace the new tag and push to remote with force flag. Always use the same version number as the one in the Xcode project, if it did not change, use the same tag as the previous release. +5. **Back to Master**: Switch back to `master` branch for further development. + +**CRITICAL**: Each time you push to the `release` branch make sure to **always** tag it with current project version. This is crucial for maintaining a clear version history. + +--- + +**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple app projects, so avoid adding project-specific information here. Refer to README.md and the source code for project-specific details. \ No newline at end of file From e847029062dfb9c74c1c7ce1ea091a8989de1b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 11 Sep 2025 12:31:05 +0200 Subject: [PATCH 06/29] Updated project [SKIP-CI] --- AGENTS.md | 41 +++++------------------------------------ 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 78ad2c6..74aeb02 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,19 +1,13 @@ # Agents -This file provides critical development and deployment guidelines for AI agents working with this app project. +This file provides development guidelines for AI agents working with this Swift Package Manager project. ## Development -### Project Structure -- App-specific logic belongs in framework targets. -- **External SwiftPM dependencies** contain shared logic and helper functions. -- UI code should be separated from business logic. -- For app development focus on the **Xcode project**. Package.swift typically supports building CLI tools only. - ### Code Patterns - Use appropriate logging frameworks instead of print statements. - Follow protocol-oriented design patterns. -- **CRITICAL: Always check FoundationKit, LoggerKit, SwiftUIKit, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication. +- **CRITICAL: Always check FoundationKit, LoggerKit, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication. #### Core Frameworks Extensions Examples - **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer @@ -23,36 +17,11 @@ This file provides critical development and deployment guidelines for AI agents - **Process**: Enhanced execution utilities with output capture - **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app preferences -### Working with External SwiftPM Dependencies -- External dependencies are separate SwiftPM repositories shared across multiple apps. -- Changes to core models, utilities, business rules must be made in their respective packages (typically you can find them in the project parent directory). -- To build or test a SwiftPM project use `DeveloperBuildTool` instead of `swift build`. -- Workflow: Edit external package → Build → Commit & Push → Return to main project → Reload package dependencies → Rebuild. - -### Verification Requirements +### Building and Testing +- To build or test this SwiftPM project, use `DeveloperBuildTool [--test]` instead of `swift build` or `swift test`. - **Always verify changes work** by building or testing before considering task complete. -- Build or test external dependencies if changes were made to them. -- Build main project to ensure all changes integrate properly. -- Run the application when possible to ensure functionality works as expected. Make sure to terminate any running instances of the app before running again. - Changes are not complete until successfully built and verified. -### Development Workflow -- All development work is done from the `master` branch. -- By default, feature development, bug fixes, and general improvements happen directly on `master`. -- Optionally, `feature/` or `fix/` branches can be created for parallel work. - -## Deployment - -### Release Process -For app releases: -1. **Version Update**: Update the Xcode project Marketing Version setting and / or the `CFBundleShortVersionString` in all relevant Info.plist files. Use the standard versioning format (eg. `1.0`, then `1.1` or `1.0.1` for minor updates). Ensure the version is consistent across all targets (app, extensions, frameworks). -2. **Commit Changes**: Add, commit, and push all pending changes to `master` branch. -3. **Merge to Release**: Switch to `release` branch, merge from `master`, and push to remote. Note: Only the `release` branch is linked to Xcode Cloud for automated builds. -4. **Tag Release**: After pushing to `release` branch, you MUST ALWAYS create and push a git tag. Use the `v1.0` or `v1.0.1` format for tags and a message like `ProjectName v1.0.1`. Create or replace the new tag and push to remote with force flag. Always use the same version number as the one in the Xcode project, if it did not change, use the same tag as the previous release. -5. **Back to Master**: Switch back to `master` branch for further development. - -**CRITICAL**: Each time you push to the `release` branch make sure to **always** tag it with current project version. This is crucial for maintaining a clear version history. - --- -**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple app projects, so avoid adding project-specific information here. Refer to README.md and the source code for project-specific details. \ No newline at end of file +**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to README.md and the source code for project-specific details. \ No newline at end of file From 253ee2b1f37d5bf18fd8f88fcd1f36e95c2c4fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 11 Sep 2025 12:38:18 +0200 Subject: [PATCH 07/29] Updated project [SKIP-CI] --- AGENTS.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 74aeb02..364c34b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,23 +5,26 @@ This file provides development guidelines for AI agents working with this Swift ## Development ### Code Patterns + - Use appropriate logging frameworks instead of print statements. - Follow protocol-oriented design patterns. -- **CRITICAL: Always check FoundationKit, LoggerKit, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication. +- **CRITICAL: If listed on Package.swift, always check FoundationKit, LoggerKit, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication (typically you can find them in the project parent directory). #### Core Frameworks Extensions Examples + - **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer -- **NSAppleScript**: `execute()` method with proper Swift error handling +- **NSAppleScript**: `execute()` method with proper Swift error handling - **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()` - **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening - **Process**: Enhanced execution utilities with output capture - **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app preferences ### Building and Testing -- To build or test this SwiftPM project, use `DeveloperBuildTool [--test]` instead of `swift build` or `swift test`. + +- To build or test this SwiftPM project, use `DeveloperBuildTool [--test]` (if available) instead of `swift build` or `swift test`. - **Always verify changes work** by building or testing before considering task complete. - Changes are not complete until successfully built and verified. --- -**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to README.md and the source code for project-specific details. \ No newline at end of file +**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to README.md and the source code for project-specific details. From 12a4bc8450ab87aae322f24b7d4b1a74243b8cbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 11 Sep 2025 12:47:05 +0200 Subject: [PATCH 08/29] Updated project [SKIP-CI] --- AGENTS.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 364c34b..9289c05 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,20 +8,19 @@ This file provides development guidelines for AI agents working with this Swift - Use appropriate logging frameworks instead of print statements. - Follow protocol-oriented design patterns. -- **CRITICAL: If listed on Package.swift, always check FoundationKit, LoggerKit, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication (typically you can find them in the project parent directory). +- **CRITICAL: If they are listed on `Package.swift`, always check `FoundationKit`, `LoggerKit`, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication (typically you can find them in the project parent directory). -#### Core Frameworks Extensions Examples +#### Core Frameworks Functionality Examples -- **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer -- **NSAppleScript**: `execute()` method with proper Swift error handling -- **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()` -- **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening -- **Process**: Enhanced execution utilities with output capture -- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app preferences +- **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer. +- **NSAppleScript**: `execute()` method with proper Swift error handling. +- **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()`. +- **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening. +- **Process**: Enhanced execution utilities with output capture. +- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app preferences. ### Building and Testing -- To build or test this SwiftPM project, use `DeveloperBuildTool [--test]` (if available) instead of `swift build` or `swift test`. - **Always verify changes work** by building or testing before considering task complete. - Changes are not complete until successfully built and verified. From 2728bfb247e9662f492c43be01c7f1976bca5c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 11 Sep 2025 13:55:19 +0200 Subject: [PATCH 09/29] Updated project [SKIP-CI] --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 9289c05..f5acd14 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,4 +26,4 @@ This file provides development guidelines for AI agents working with this Swift --- -**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to README.md and the source code for project-specific details. +**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to `README.md` file if available and the source code for project-specific details. From d030b5193d1e4e770156deb27865773b64c5695f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 18 Sep 2025 13:39:27 +0200 Subject: [PATCH 10/29] Updated project [SKIP-CI] --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index f5acd14..8b46202 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ This file provides development guidelines for AI agents working with this Swift - **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()`. - **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening. - **Process**: Enhanced execution utilities with output capture. -- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app preferences. +- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app settings. ### Building and Testing From 36a9f7f782120d0729733d604aaffc85b164d13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 18 Jun 2026 01:35:52 +0200 Subject: [PATCH 11/29] Update README package branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 83feb56..4a7fa1e 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ 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", branch: "main"), ``` ## Environment Variables From 39f823774c0258b92f8919584b37c89481ebdb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 18 Jun 2026 11:17:50 +0200 Subject: [PATCH 12/29] Fix CI Python library selection --- .github/workflows/continuous-integration.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index e265347..f6e85c3 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -14,12 +14,17 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v1 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: '3.13' + - name: Configure PythonKit + shell: bash + run: | + python_library="$(find "$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 --enable-test-discovery if: runner.os != 'Windows' env: - PYTHON_VERSION: 3 PYTHON_LOADER_LOGGING: TRUE From 4c9e63680ab40aa0cff16bd3464c3c676f6e0c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 18 Jun 2026 11:19:36 +0200 Subject: [PATCH 13/29] Follow Python toolcache symlinks in CI --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index f6e85c3..5510144 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -20,7 +20,7 @@ jobs: - name: Configure PythonKit shell: bash run: | - python_library="$(find "$pythonLocation" \( -name 'libpython3.13*.dylib' -o -name 'libpython3.13*.so' \) -print -quit)" + 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 From 91b0f7fb7ce9c83cfe6665a8194dd10cef51d19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Fri, 19 Jun 2026 02:03:44 +0200 Subject: [PATCH 14/29] Reduce shared agent instructions --- AGENTS.md | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 8b46202..ad824ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,29 +1,10 @@ # Agents -This file provides development guidelines for AI agents working with this Swift Package Manager project. +This project is a Swift Package Manager project. -## Development +For general development guidance, use the `swift-project-guide` skill. -### Code Patterns +## Project Notes -- Use appropriate logging frameworks instead of print statements. -- Follow protocol-oriented design patterns. -- **CRITICAL: If they are listed on `Package.swift`, always check `FoundationKit`, `LoggerKit`, and other core frameworks before implementing basic functionality.** These frameworks contain extensive extensions and utilities that avoid code duplication (typically you can find them in the project parent directory). - -#### Core Frameworks Functionality Examples - -- **NSError**: `NSError(description:, recoverySuggestion:)` convenience initializer. -- **NSAppleScript**: `execute()` method with proper Swift error handling. -- **ProcessInfo**: `launchExtensionsPaneInSystemSettings()`, `launchPrivacyAndSecurityPaneInSystemSettings()`. -- **URL**: `open(withAppBundleIdentifier:)` for cross-platform URL opening. -- **Process**: Enhanced execution utilities with output capture. -- **UserDefaults**: `@UserDefaults.Wrapper` property wrapper for cleaner app settings. - -### Building and Testing - -- **Always verify changes work** by building or testing before considering task complete. -- Changes are not complete until successfully built and verified. - ---- - -**IMPORTANT**: This is a generic development guide for AI agents shared and available on multiple projects, so avoid adding project-specific information here. Refer to `README.md` file if available and the source code for project-specific details. +- Read `README.md` for project-specific architecture, targets, and conventions. +- Keep project-specific guidance in `README.md` or source-local documentation, not in this shared `AGENTS.md`. From 8396f7a62980a66ad2a990d7881175055da67459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Mon, 22 Jun 2026 23:46:06 +0200 Subject: [PATCH 15/29] Restrict CI workflow to main --- .github/workflows/continuous-integration.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5510144..3823db0 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,8 +1,12 @@ name: Continuous Integration on: - - push - - pull_request + push: + branches: + - main + pull_request: + branches: + - main jobs: continuous-integration: From 94572e93f393d011249f4ce790a974c0238eea0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 2 Jul 2026 18:35:11 +0200 Subject: [PATCH 16/29] Skip CI for draft PRs [SKIP-CI] --- .github/workflows/continuous-integration.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 3823db0..af850f5 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -10,6 +10,10 @@ on: 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: From 1ac25ddddca845ba0184911de9c00e1508948cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Wed, 8 Jul 2026 13:17:34 +0200 Subject: [PATCH 17/29] Add agent skill symlinks [SKIP-CI] --- .agents/skills | 1 + .claude/skills | 1 + Agents/Instructions/.gitkeep | 0 Agents/Skills/.gitkeep | 0 CLAUDE.md | 1 + 5 files changed, 3 insertions(+) create mode 120000 .agents/skills create mode 120000 .claude/skills create mode 100644 Agents/Instructions/.gitkeep create mode 100644 Agents/Skills/.gitkeep create mode 120000 CLAUDE.md 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/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 From af828c39e5b1c7061e3016f419608690a9fed128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 27 Aug 2026 13:30:12 +0200 Subject: [PATCH 18/29] Fix README grammar and formatting [SKIP-CI] --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4a7fa1e..ddefa9a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# PythonKit +# PythonKit [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pvieito/PythonKit) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pvieito/PythonKit) @@ -39,7 +39,7 @@ Add the following dependency to your `Package.swift` manifest: ## Environment Variables -As the Python library are loaded at runtime by **PythonKit**, it will try to find the most modern Python version available in the system. You can force a given version with the `PYTHON_VERSION` environment variable or an specific Python library path or name with `PYTHON_LIBRARY`. +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`. ``` $ PYTHON_VERSION=3 swift run @@ -64,7 +64,7 @@ Fatal error: Python library not found. Set the PYTHON_LIBRARY environment variab ## Troubleshooting -- If your are targeting the Mac platform with the [Hardened Runtime](https://developer.apple.com/documentation/security/hardened_runtime) enabled make sure you are properly signing and embedding the Python framework you are trying to load with **PythonKit**. The Hardened Runtime [Library Validation mechanism](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation) prevents a process from loading libraries that are not signed by Apple or the same developer as the main process. +- 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 From 71cf18aed89cdb2790f916082c5cd4e222b173d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Thu, 27 Aug 2026 16:44:43 +0200 Subject: [PATCH 19/29] Polish README grammar and code blocks [SKIP-CI] --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ddefa9a..a9cc22c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - # PythonKit + [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/pvieito/PythonKit) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fpvieito%2FPythonKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/pvieito/PythonKit) @@ -17,7 +17,7 @@ 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 @@ -41,7 +41,7 @@ Add the following dependency to your `Package.swift` manifest: 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 $ PYTHON_VERSION=2.7 swift run @@ -54,7 +54,7 @@ $ PYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython2.7.so swift run 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'... @@ -66,7 +66,6 @@ Fatal error: Python library not found. Set the PYTHON_LIBRARY environment variab - 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 - Originally **PythonKit** was based on the `Python` module from the [**Swift for TensorFlow**](https://github.com/tensorflow/swift) experimental project. From ee6cb825d43c33dd84c1462563ca60d9e4dfb0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Mon, 7 Sep 2026 20:19:06 +0200 Subject: [PATCH 20/29] Align Swift tools and deployment requirements [SKIP-CI] --- .github/workflows/continuous-integration.yml | 21 +++++++++++++++++--- Package.swift | 12 ++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index af850f5..dc23b53 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,11 +16,26 @@ jobs: !contains(github.event.pull_request.title || '', '[SKIP-CI]') strategy: matrix: - os: - - ubuntu-latest - - macos-latest + include: + - os: ubuntu-24.04 + - os: macos-26 + xcode: "26.0.1" + - os: macos-26 + xcode: latest-stable runs-on: ${{ matrix.os }} steps: + - name: Set Up Swift + if: runner.os == 'Linux' + uses: swift-actions/setup-swift@v2 + with: + swift-version: "6.2" + - name: Select Xcode + if: runner.os == 'macOS' + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: ${{ matrix.xcode }} + - name: Show Swift Version + run: swift --version - uses: actions/checkout@v1 - uses: actions/setup-python@v6 with: diff --git a/Package.swift b/Package.swift index c1ea0aa..5f17acc 100644 --- a/Package.swift +++ b/Package.swift @@ -1,9 +1,12 @@ -// swift-tools-version:5.1 +// swift-tools-version:6.2 import PackageDescription let package = Package( name: "PythonKit", + platforms: [ + .macOS(.v26), + ], products: [ .library( name: "PythonKit", @@ -17,7 +20,10 @@ let package = Package( ), .testTarget( name: "PythonKitTests", - dependencies: ["PythonKit"] + dependencies: [ + "PythonKit", + ] ), - ] + ], + swiftLanguageModes: [.v5] ) From 405f70283fdf4ee516b28081a3c7da2ae0987be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Tue, 8 Sep 2026 20:06:54 +0200 Subject: [PATCH 21/29] Restore latest GitHub Actions runners [SKIP-CI] --- .github/workflows/continuous-integration.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index dc23b53..10c44db 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,10 +17,10 @@ jobs: strategy: matrix: include: - - os: ubuntu-24.04 - - os: macos-26 + - os: ubuntu-latest + - os: macos-latest xcode: "26.0.1" - - os: macos-26 + - os: macos-latest xcode: latest-stable runs-on: ${{ matrix.os }} steps: From 244737f933ad20178412c0b17fd163ba3b3dad43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Tue, 8 Sep 2026 20:11:16 +0200 Subject: [PATCH 22/29] Restore simple operating-system CI matrix [SKIP-CI] --- .github/workflows/continuous-integration.yml | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 10c44db..2db337b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,12 +16,9 @@ jobs: !contains(github.event.pull_request.title || '', '[SKIP-CI]') strategy: matrix: - include: - - os: ubuntu-latest - - os: macos-latest - xcode: "26.0.1" - - os: macos-latest - xcode: latest-stable + os: + - ubuntu-latest + - macos-latest runs-on: ${{ matrix.os }} steps: - name: Set Up Swift @@ -29,11 +26,6 @@ jobs: uses: swift-actions/setup-swift@v2 with: swift-version: "6.2" - - name: Select Xcode - if: runner.os == 'macOS' - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: ${{ matrix.xcode }} - name: Show Swift Version run: swift --version - uses: actions/checkout@v1 From 3535165034a1f05be5b1f2355a5b6e9a63d95bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jos=C3=A9=20Pereira=20Vieito?= Date: Wed, 9 Sep 2026 01:25:34 +0200 Subject: [PATCH 23/29] Update Package.swift --- Package.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Package.swift b/Package.swift index 5f17acc..2a87b41 100644 --- a/Package.swift +++ b/Package.swift @@ -4,9 +4,6 @@ import PackageDescription let package = Package( name: "PythonKit", - platforms: [ - .macOS(.v26), - ], products: [ .library( name: "PythonKit", From 690d18f3f4f5ac13e5a4b24d4e0fce8e592d145c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jos=C3=A9=20Pereira=20Vieito?= Date: Wed, 9 Sep 2026 01:26:22 +0200 Subject: [PATCH 24/29] Update Swift tools version to 6.0 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 2a87b41..2f5b7f0 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:6.2 +// swift-tools-version:6.0 import PackageDescription From a5b6fae118c113ee554209559abe089d96155e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Wed, 9 Sep 2026 19:26:40 +0200 Subject: [PATCH 25/29] Add public project marker [SKIP-CI] --- .public_project | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .public_project diff --git a/.public_project b/.public_project new file mode 100644 index 0000000..e69de29 From c25790da853916dac6039e58efd5db1f2d01858c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Wed, 9 Sep 2026 21:06:03 +0200 Subject: [PATCH 26/29] AGENTS update [SKIP-CI] --- AGENTS.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ad824ff..2538ff9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,10 +1,16 @@ # Agents -This project is a Swift Package Manager project. - -For general development guidance, use the `swift-project-guide` skill. +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 this shared `AGENTS.md`. +- 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. From 27d59187de6722c5011acc3cb319a6e5e31bb815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Fri, 11 Sep 2026 13:34:35 +0200 Subject: [PATCH 27/29] Project update [SKIP-CI] --- .open_source | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .open_source diff --git a/.open_source b/.open_source new file mode 100644 index 0000000..e69de29 From e1d34f95c58189b771bfc8423b9b2cf8fc651664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Fri, 11 Sep 2026 13:37:19 +0200 Subject: [PATCH 28/29] PythonKit v1.0 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a9cc22c..8174faa 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ 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: "main"), +.package(url: "https://github.com/pvieito/PythonKit.git", from: "1.0.0"), ``` ## Environment Variables From fe7d97334600b0fc11fbef5e0525f93eaa68ef7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Jose=CC=81=20Pereira=20Vieito?= Date: Fri, 23 Oct 2026 10:35:46 +0200 Subject: [PATCH 29/29] Use preinstalled runner Swift on Linux CI --- .github/workflows/continuous-integration.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 2db337b..a05389d 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -21,11 +21,6 @@ jobs: - macos-latest runs-on: ${{ matrix.os }} steps: - - name: Set Up Swift - if: runner.os == 'Linux' - uses: swift-actions/setup-swift@v2 - with: - swift-version: "6.2" - name: Show Swift Version run: swift --version - uses: actions/checkout@v1 @@ -39,7 +34,6 @@ jobs: test -n "$python_library" echo "PYTHON_LIBRARY=$python_library" >> "$GITHUB_ENV" - name: Test - run: swift test --enable-test-discovery - if: runner.os != 'Windows' + run: swift test env: PYTHON_LOADER_LOGGING: TRUE