Python interop with PythonKit

I've been really interested in the Python integration provided by Swift for Tensorflow and I got the impression from reading the README that PythonKit (GitHub - pvieito/PythonKit: Swift framework to interact with Python.) should work on regular swift. However I'm getting an error: error: no such module 'PythonKit'. I'm using the latest dev version of swift (2018-08-29). Are there any requirements I need to fulfil to be able to use it? I installed it on a Ubuntu docker image (modified from the official ones to use the dev toolkit).

Also does anyone know if there's a way to be able to import a local python project with it? I guess I could install it on the global interpreter with pip install -e /path/to/module/ but it's less than ideal.

1 Like

I haven’t tried PythonKit, but its read‐me instructions are just basic package dependency set‐up.

Depending on how far you have gotten already, general information about the package manager can be found here, and details about its manifest format are here.

There could be many other causes for “no such module”, but I hypothesize the following: If you just followed PythonKit’s instructions to the letter, pasting their snip‐it into your manifest, then you would still have to declare a dependency from your target to their product. (At this point the package manager knows that you want to use their package and at what version, but it doesn’t know which of your modules are supposed to depend on which of their products.) Notice how each target also has its own dependency declaration in the example at the top of the manifest API documentation.

1 Like

Wow, can't believe I missed something so simple. In hindsight I should have chosen a more mainstream library as my first import so I got familiar with SPM. It worked flawlessly thanks @SDGGiesbrecht.

Does anyone know if you can import local python modules like you can do with C?

I am not familiar with PythonKit (or even Python for that matter), but the example in the PythonKit read‐me contains a code translation example with these lines:

Python:

import sys

Swift:

let sys = try Python.import("sys")

There are more such code translation examples here that involve other modules.

If that does not fully answer your question, you should probably ask the PythonKit folks instead.

Sure, I've seen all these but they are all examples of using libraries installed in the global interpreter. Python is different to other languages in this regard since when you install a library with pip it gets installed globally not just for your project (unless you use a virtualenv). Those are the ones I can import easily. That's why I suggested that I can install a module into my global interpreter so I can import it but it's not ideal. What I wanted to do is to be able to import python files relative to my path.

Still I found a way to do it. Python can do relative imports to the path of your file, but this doesn't work from swift. The other way is to add the base path of your python file to the PYTHONPATH which you can do from swift. Say you have a python file called aaa.py with a function named hello on the root of your swift project, you can call it from swift like so:

let os = Python.import("os")
let sys = Python.import("sys")
sys.path.append(os.getcwd())

let aaa = Python.import("aaa")
aaa.hello()

In case it helps anyone.

4 Likes

I can't get this to work, unfortunately. What it gives me is the current directory in DerivedData, which doesn't have my module. Adding the home folder to PYTHONPATH works using pathlib:

        let pathLib = Python.import("pathlib")
        let home = pathLib.Path.home()
        sys.path.append(home)
        print("sys.path: \(sys.path)")

So I put my module there, but I still can't load classes from it (I've double-checked from the terminal, and it works fine there).

        let myModule = try? Python.attemptImport("myModule")

leaves myModule as nil.
I also tried:

        let myClass = try? Python.attemptImport("myModule.myClass")

A weird additional detail about this is that, even though myModule isn't recognized, I do see a .pyc file for myClass appear in the __pycache__. Strange, no?

UPDATE: Just to get going, I've tried installing myModule via pip, but it still fails with a No module named ... error in PythonKit. Maybe some kind of permissions issue? This is a macOS app, with no Sandbox or Hardened Runtime capabilities added, and I'm running in debug mode only (I'm just building an in-house utility app, at the moment). I have changed my python version to a conda virtualenv using setenv, but I'm double-checking my work in the terminal, running that virtualenv, so things should be fine, unless it's some specific problem that PythonKit has with virtualenvs.

UPDATE 2: Interestingly, it seems I can get it loading by just using a String for my home folder, not pathlib. No idea why that would be (probably some other issue I'm not seeing), but at least I'm on to my next error! Haha... My module is trying to use cuda, which isn't on this machine, so have to edit all the .device stuff for macOS.

One problem I appear to be having is with dependencies of my custom module not loading. How are these supposed to be handled? Should we import them explicitly in Swift, even if we don't call them directly from our Swift code?

I put my swift project target path into info.plist, then I retrieve that path out at build time to construct a path to my python scripts folder.

var pyBuildPath: String {
    var s  = "/"
    s += Bundle.main.infoDictionary?["TargetName"] as! String // add TargetName == $(TARGET_NAME) in info.plist
    s += ".app/Contents/Resources/py_scripts" // relative path within my swift project containing py scripts
    return s
}

var pyWorkingDir: String {
    return setupPy()
}

var pyCode: PythonObject {
    return Python.import("pycode") // my python module/code
}

func setupPy() -> String {
    let sys = Python.import("sys")
    let pyPath = pydir() + pyBuildPath
    sys.path.append(pyPath)
    return pyPath.description
}

func pydir() -> String {
    let os = Python.import("os")
    let pyDir = os.getcwd()
    return pyDir.description
}

Then in my ContentView.swift file I also grabbed the reference to my python module via the app delegate:

var pyCode: PythonObject {
    let a = NSApplication.shared.delegate as! AppDelegate
    return a.pyCode
}

Then I could simply call functions and retrieve variables from the python code with things like:

let p = pycode.pyFunctionName()
let p = pyCode.pyVariable

...etc...

1 Like

Hi, I'm struggling with the same problem but I am not able to resolve the import error. I added the dependency as follows

// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "dsp",
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "dsp",
            targets: ["dsp"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/pvieito/PythonKit.git", .branch("master")),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "dsp",
            dependencies: ["PythonKit"]),
        .testTarget(
            name: "dspTests",
            dependencies: ["dsp"]),
    ]
)

and then ran swift build. This completed without errors but when I do import PythonKit I get an error error: no such module 'pythonkit'. I'm not sure what I'm missing here.