Python Backend alongside MacOS Swift application

Context/Project Idea:

I'm currently developing a project that consists of a macOS application using Swift and a local Python backend that executes specific tasks such as processing data. The Python backend is the core of this project, while the Swift application is a mere interface to interact with it.

These two project parts should be decoupled so the user can theoretically run their own backend and connect the Swift application to it. Likewise, the user should be able to connect to the shipped backend using, e.g. curl.

Current plan:

My main idea is to use launchctl to launch a launchd agent which runs the Python backend. The script launching the backend will generate an API key stored in a keychain access group. The Swift application can then get that key and access the backend. The user can always get that API key from the keychain if they want to connect to it programmatically.

Here are the main questions I have currently:

  1. Python Interpreter Consistency: I'm exploring options such as cx_Freeze or PyInstaller to create a standalone Python executable for better system stability. Does anyone have experience with these tools in a macOS environment, or are there other reliable alternatives worth considering?
  2. Adding a Launchd Agent to Xcode: How can I add a launchd agent to my Xcode project to manage a Python executable built with cx_Freeze or PyInstaller? What steps should I follow to ensure it functions properly?
  3. Keychain Access for Launchd Agent: Is it feasible for a launchd agent to access a Keychain access group? What configurations or permissions are necessary to implement this?

Thanks in advance!

I don’t see anything showstoppers in your proposed setup. It won’t necessarily be easy to execute, but it should be feasible.

Regarding your specific questions:

Python Interpreter Consistency

This is very specific to Python and I’m not really the right person to advise on that.

Adding a Launchd Agent to Xcode

First up, I recommend that you install the agent via SMAppService rather than by manually running launchctl. That’s a lot easier.

The main difficulty you’ll face is in packaging. You should strive to follow the rules in Placing Content in a Bundle. You’ll probably want to put the agent into an app-like wrapper (due to the keychain setup discussed below), which means the agent as a whole gets nested in either Contents/MacOS or Contents/Helpers.

If you do follow those rules, Xcode’s automatic code signing should do the right thing when it comes to code signing, entitlements, provisioning profiles, and so on.

Depending on how complex your Python setup is, you may end up needed some of the tricks described in Embedding nonstandard code structures in a bundle.

Keychain Access for Launchd Agent

There shouldn’t be any problem with your launchd agent using the keychain. Before you start, I recommend that you read TN3137 On Mac keychain APIs and implementations, which explains some critical background.

My general advice is that you use the data protection keychain. This is a bit tricky for your launchd agent because it’ll have to be placed in an app-like wrapper. Still, that’s not super hard. Signing a daemon with a restricted entitlement explains the basic process.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

First of all thanks for all the information.

This is very specific to Python and I’m not really the right person to advise on that.

Fair enough, I'll seek advice elsewhere for that.

[quote='809677022, DTS Engineer, /thread/766464?answerId=809677022#809677022']

I recommend that you install the agent via SMAppService

I recommend that you install the agent via SMAppService

.

├── frozen_application_license.txt

├── main

├── lib/

└── share/

  1. lib/: Contains a bunch of .so and .pyc files. It also includes a Python executable and some .dylib files.

  2. share/: Some binary files for zone_info.

Due to its nested structure, I struggled to automatically code-sign the files using Xcode. I've used a build phase script to do that manually.

If I move all that content into Contents/MacOS, the final app cannot sign. However, if I move only the main executable into Contents/MacOS and symlink the rest from Contents/Resources/lib -> Contents/MacOS/lib, I can run the main application and run the launchd agent successfully.

I don't know if that's a valid approach.

[quote='809677022, DTS Engineer, /thread/766464?answerId=809677022#809677022']

...it’ll have to be placed in an app-like wrapper.

[/quote]

Fair enough. Suppose I put the agent into an app-like structure. How exactly would I nest that into my existing application? The agents .plist would need to be inside Contents/Library/LaunchAgents of the main application. How about the rest?

...it’ll have to be placed in an app-like wrapper.

First of all, thanks for all the information.

This is very specific to Python and I’m not really the right person to advise on that.

Fair enough, I'll seek advice elsewhere for that.

I recommend that you install the agent via SMAppService

I've created a sample project and have been doing precisely that. I followed the Embedding a command-line tool in a sandboxed app with a slight variation: no sandboxing and including a .plist for the launchd agent at Contents/Library/LaunchAgents

I've been using cx_freeze to build the application, and it produces the following structure:

.
├── frozen_application_license.txt
├── main
├── lib/
└── share/
  1. lib/: Contains a bunch of .so and .pyc files. It also includes a Python executable and some .dylib files.
  2. share/: Some binary files for zone_info.

Due to its nested structure, I struggled to automatically code-sign the files using Xcode. I've used a build phase script to do that manually. Additionally, I'm unsure if it's possible to split up the files correctly to achieve the wanted structure as explained in Placing content in a bundle

What works is when I move only the main executable into Contents/MacOS and create symlinks for the rest: e.g. Contents/MacOS/lib -> Contents/Resources/lib, I can run the main application and run the launchd agent successfully. This feels similar to Use symlinks for gnarly edge cases in Embedding nonstandard code structures in a bundle. I'm not sure if that's a valid approach in this case, though.

...it’ll have to be placed in an app-like wrapper.

Fair enough. Suppose I put the agent into an app-like structure. How exactly would I nest that into my existing application? The agents .plist would need to be inside Contents/Library/LaunchAgents of the main application right? How would I put the launchd app-like structure into the existing app structure?

Thanks!

Python Backend alongside MacOS Swift application
 
 
Q