How to import Python functions to C in Xcode? - python

I'm trying to combine C and Python for research reasons but I'm getting problems in my code. I used the code from a tutorial but it seems that the code for getting the python file is not working. Here's the code:
helloClass.py
def sayHello(name):
return "Hello " + name + "!"
main.c
int main(int argc, const char * argv[]) {
char name[50];
printf("What's your name?\n");
fgets(name, 64, stdin);
char *answer = NULL;
PyObject *modname, *mod, *mdict, *func, *stringarg, *args, *rslt;
Py_Initialize();
PySys_SetPath(Py_GetPath());
modname = PyString_FromString("helloClass");
mod = PyImport_Import(modname);// Here mod = null because it can´t find it.
if (mod){
mdict = PyModule_GetDict(mod);
func = PyDict_GetItemString(mdict, "sayHello");
if (func){
if (PyCallable_Check(func)) {
stringarg = PyString_FromString(name);
args = PyTuple_New(1);
PyTuple_SetItem(args, 0, stringarg);
rslt = PyObject_CallObject(func, args);
if (rslt) {
answer = PyString_AsString(rslt);
Py_XDECREF(rslt);
}
Py_XDECREF(stringarg);
Py_XDECREF(args);
}
Py_XDECREF(mdict);
Py_XDECREF(func);
}
Py_XDECREF(modname);
Py_XDECREF(mod);
}
printf("%s", answer);
answer = NULL; }

There's not enough information here to give you the complete answer, but the basic problem is almost certainly that helloClass.py does not end up anywhere on the module search path, so PyImport_Import can't find it.
For example, maybe you're building your main executable into a location like ~/Library/Developer/Xcode/DerivedData/Hello/Build/Products/Release/hello, but not copying helloClass.py there. Or, maybe you are copying it there, but the equivalent of dirname $0 isn't on your module path. Or… well, there are a million possibilities, but the answer is the same in every case:
Figure out where the executable and the Python file are getting installed to.
Build an appropriate (probably relative) path out of that knowledge.
Make sure that path gets added to the module path.
The exact details on how Python searches for modules is different between different versions, but the basic idea is explained in The importstatement (or the 3.3 version, which defers to The import system), and often, it's just a matter of adding something to sys.path.
(Note that if you're building .app bundles instead of just Unix command-line executables, there is a standard answer to this—you stick the .py files in a standard place, use Cocoa or CoreFoundation APIs to get the path, and then use that. But that doesn't seem to be relevant here.)
Looking at the project you uploaded at https://github.com/Eduardof0nt/help.git:
You're not copying helloClass.py anywhere. You have to decide where you want to copy it, and that depends entirely on how you want to deploy things. But for debugging purposes, just to get something testable, you can just put it alongside the Hello Python! executable (in build/Debug or build/Release). To do that:
Select the project in the Project Navigator.
Select the target in the left sidebar of the project view that appears.
Select the Build Phases tab.
Click the Add Build Phase button.
Add a Copy Files phase.
Set its Destination to Products Directory.
Drag helloClass.py from the Project Navigator into the build phase.
Now, when you build, if you look at the output (turn on "All" and "All Messages"), you'll see "Copy build/Debug/helloClass.py" right after the "Link build/Debug/Hello Python!". And, if you look inside that directory, you'll see the two files side by side.
But that doesn't solve your whole problem. Because /wherever/you/started/build/Debug is not going to be on the sys.path that Python uses. The quickest way around this is to call Py_SetProgramName(argv[0]) right before the Py_Initialize(), and PySys_SetArgvEx(argc, argv, 1) right after. That may well not be the right thing to do for your use case, but you're going to have the read the documentation and understand what all of this does, because I can't possibly explain all the details of embedding Python in an SO answer.
While you're trying to learn this stuff, you probably want to do some debugging. For example, you can printf(path=%s\n", Py_GetPath()) to see what the sys.path equivalent is, and PyObject_Print(mod, stdout, 0) to see what's in mod, and so on. But really, you're going to want to use the Xcode debugger—add a breakpoint and try doing this stuff at runtime.
Much of this is explained in Extending and Emnbedding the Python Interpreter, and you really do need to read that; there's no substitute. (You don't need to read the whole Python/C API Reference Manual, but you will end up reading a lot of it, and getting good at searching it, before you're done.) However, because embedding Python is much less common than extending it, the docs really don't include everything you need, so… if whatever tutorial you're using doesn't cover things like PySys_* and Py_GetPath, you may need to find a better tutorial first.

Related

ModuleNotFoundError: No module named 'encodings' while running python in iOS

I am trying to integrate python in iOS. I tried same thing as mentioned here - https://github.com/beeware/Python-Apple-support/tree/3.9
Here is my python script in Xcode projects
func RunPythonScript() -> PythonObject {
if let path = Bundle.main.path(forResource:"/Users/projects/extra/python_apple_support/PAS_10_11_v3/PAS_10_11_v3/Resources/",
ofType: nil) {
setenv("PYTHONPATH", path, 1)
setenv("PYTHONHOME", path, 1)
}
let sys = Python.import("sys")
sys.path.append("/Users/projects/extra/python_apple_support/PAS_10_11_v3/PAS_10_11_v3/PAS_10_11_v3/")
let file = Python.import("pythonscript")
let response = file.hello_world()
print(response)
return response
}
It builds successfully but when I call python program it end up saying
Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Python path configuration:
PYTHONHOME = (not set)
PYTHONPATH = (not set)
program name = 'python3'
isolated = 0
environment = 1
user site = 1
import site = 1
sys._base_executable = '/Users/karimkhan/Library/Developer/CoreSimulator/Devices/C444D135-C393-4631-AFE2-FF5F86935EF6/data/Containers/Bundle/Application/642E9540-CBAF-448C-8E8D-856B8E5D03EC/PAS_10_11_v3.app/PAS_10_11_v3'
sys.base_prefix = '/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14'
sys.base_exec_prefix = '/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14'
sys.platlibdir = 'lib'
sys.executable = '/Users/karimkhan/Library/Developer/CoreSimulator/Devices/C444D135-C393-4631-AFE2-FF5F86935EF6/data/Containers/Bundle/Application/642E9540-CBAF-448C-8E8D-856B8E5D03EC/PAS_10_11_v3.app/PAS_10_11_v3'
sys.prefix = '/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14'
sys.exec_prefix = '/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14'
sys.path = [
'/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14/lib/python39.zip',
'/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14/lib/python3.9',
'/Users/runner/work/Python-Apple-support/Python-Apple-support/install/iOS/iphonesimulator.x86_64/python-3.9.14/lib/lib-dynload',
]
Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
Python runtime state: core initialized
ModuleNotFoundError: No module named 'encodings'
Current thread 0x0000000108fd4600 (most recent call first):
<no Python frame>
I just got this working after days of trying! Using Python 3.11.
For anyone wondering, we're using a version of Python patched for iOS (from Beeware's Python Apple Support), and PythonKit (Swift pkg) to embed Python in an iOS app using Xcode. The goal (for me at least) is to do my UI in SwiftUI, but use my own Python logic on some of the app's data.
OP, I don't know what all of your problems might be (there could be several), but I am immediately noticing at least one thing wrong. You're looking on your mac for Python when your goal is to put Python itself inside of your app. You should be looking in the app for the directory containing the Python standard library ('python-stdlib'), as well as the 'lib-dynload' subdir, provided to you by Beeware's Python Apple Support repo. You want to set PYTHONPATH and PYTHONHOME to this combo of paths.
This post is intended to supplement to "How to embed a Python interpreter in an iOS app - presented by Łukasz Langa." on YT.
In the video the OP linked to, Lukasz's 'python-stdlib' has a different name and a very different directory structure than the latest version of Py for iOS provides. What you'll need to figure out is the path to the 'python-stdlib' dir, AND [probably] its subdir, 'lib-dynload'. Counterintuitively, you do need to specify both, even though the latter is a subdir of the former.
Your line:
if let path = Bundle.main.path(forResource:"/Users/projects/extra/python_apple_support/PAS_10_11_v3/PAS_10_11_v3/Resources/",
ofType: nil) {
Should read something like:
if let libPath = Bundle.main.path(forResource: "python-stdlib", ofType: nil),
let libPath2 = Bundle.main.path(forResource: "python-stdlib/lib-dynload", ofType: nil) {
let mergedPaths = "\(libPath):\(libPath2)"
...where 'python-stdlib' is the PATH of the python libraries directory--not just the directory name. (If that's confusing to a newbie, this is the same: let mergedPaths = libPath + ":" + libPath2.
To get an idea of the path, go to Product > Show Build Folder in Finder, then find Products/Debug-iphonesimulator, right click .app (the greyed / X'd out icon) > Show package contents.
Assuming you have properly copied this stuff into the project, you should be able to find 'python-stdlib' inside the .app. For me, it was right in the root of the .app (since that's where I effectively put it, by adding it to the GROUP (not dir!) called 'Resources'. (Because Resources is a group, whatever is in there will be in the root level of the .app, NOT in a dir called Resources. It seems there can be groups that are backed by folders, and groups that are not. This is an important distinction.)
Now, set PYTHONPATH and PYTHONHOME to mergedPaths like you were doing:
setenv("PYTHONHOME", mergedPaths, 1)
setenv("PYTHONPATH", mergedPaths, 1)
If you don't include the path to 'lib-dynload', then this will only work in the simulator. Why would it work at all? Lovely question... 🤷🏽
IMPORTANT: The script.py file Lukasz created for his custom Python code must go into the 'python-stdlib' directory for this to work. I imagine you could stick it anywhere, as long as you append its new path to mergedPaths, as can be seen in a project generated by Beeware's Briefcase. To append that path to the merged paths, you're just concatenating strings with a ":" between each path. I have not tested this.
COPYING THE LIBRARY PROPERLY
Here's something that screwed me up for a while:
When you copy over Python.xcframework and the std-lib, make sure you're creating folders and not groups (in the prompt after you drag and drop), else you will end up with hundreds of errors due to the flattening of directories resulting from creating groups and not dirs (think of every main.py from the lib ending up in the same folder--no bueno). Make sure your prompt looks exactly like this:
screenshot of said prompt ☝🏽
SIGNING THE PYTHON LIBRARY
Follow 'The Manual Way' > Step 6 # Python Apple Support > USAGE.md.
All I had to change was this line segment:
"$CODESIGNING_FOLDER_PATH/Contents/Resources/python-stdlib/lib-dynload"
Due to my directory structure ('python-stdlib' in root of .app), I changed it to:
"$CODESIGNING_FOLDER_PATH/python-stdlib/lib-dynload"
...omitting the extra directories in the path to 'lib-dynload'.
ALMOST FORGOT--module.modulemap!
Assuming you have created 'module.modulemap', and copied it to all three locations (in the Xcode project and in both Headers dirs in the framework), you'll want it to read like this:
module Python {
umbrella header "Python.h"
export *
link "python-stdlib"
}
...where Lukasz's read link Python, ours must read link python-stdlib, as above. I wouldn't be surprised this is actually the path to 'python-stdlib'. Again, mine is right in the root of the .app, so under that assumption, the name is also the path, here. I have not tested this theory.
NOT NECESSARILY RELEVANT TO THE SOLUTION BUT MAYBE ENTERTAINING
This part is pretty amazing. I've had this working in the simulator only for the last several days. For most of that time, on the device, it would import Random, but not Math (which was being called from Random). Then I figured how to point the app to both 'python-stdlib' and 'python-stdlib/lib-dynload'. Got a new error -- code signature invalid for '.../math.cpython-311-iphoneos.so' (that Python math module!) in 'lib-dynload'! Wow! It was finally SEEING the math module.
PARTIAL CONSOLE OUTPUT:
...(code signature in <45B34416-425D-3E01-BC39-CB7A8C170A0A> '/private/var/containers/Bundle/Application/1465B572-3399-4B76-B017-4EE168637AF5/SIXTH_try.app/python-stdlib/lib-dynload/math.cpython-311-iphoneos.so' not valid for use in process: mapped file has no cdhash, completely unsigned? Code has to be at least ad-hoc signed.)
Here's the amazing part... YESTERDAY, I told Github to tell me about any changes to Beeware's Python Apple Support. Last night at 7pm, I get a notification that dude updated the USAGE.md to add instructions for code signing! Ha! What are the chances that this would happen on exactly the day that I need it to happen. Anyway, I followed those instructions, ran on my phone, and WHUHBAM! MY IPHONE IS USING THE RANDOM MODULE TO GENERATE INTS UPON A BUTTON TAP! THE WHOLE GUI WAS MADE IN SWIFTUI. HOLY MOTHER#(#)!#$ IT'S WORKING! (I realize enthusiasm and emotion are not always welcome on SO, but I'm going to celebrate, and no one will stop me!)

Executing Python Script from Windows Forms .NET

I am fairly new to Python and .NET in general, but decided to ask more competent people, since I have been struggling with the issue of executing python script from Windows Forms.
The basic idea of my project is a desktop applicaton and the overall logic would be to read from a couple of selected check boxes, pass the values of those selections to my python script, from there I generate an excell table based on those results, and display this table back into the Windows Forms application.
Creating the table and managing to display it in the Desktop App is already done, but I am having serious issues with the communication between the two platforms, when it came to executing the script itself.
I have tried using IronPython and it worked perfectly, untill the fact that I found that Iron Python does not support CPython packages, like Pandas, which is build on numpy, and numpy apparantly is one of those packages. I looked over a lot of articles about this issue and the answers did not seem promising and most of the suggestions were to use pythonnet.
I tried to implement pythonnet, following numerous articles and all I managed to do, besides creating a bigger mess, is nothing as a result.
Finally, I decided to use C# Process class, but did not succeed also.
Would appreciate if there are any comments and suggestions on how to remedy this issue.
Python version: 3.7
Windows 10 (64 bit)
.NET Framework 4.7.2
Here is some of my code attempts in Windows Forms:
Implementation with the usage of the Process Class
Issue here is that I am not able to run this script due to the error messages that it cannot find the packages for the python script
var processStartInfo = new ProcessStartInfo
{
Arguments = "C:\\Users\\Dobromir\\PycharmProjects\\pythonProject\\main.py",
FileName = "C:\\Python27\\python.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process.Start(processStartInfo);
Implementation using IronPython (which was working before the usage of pandas package)
Issue here is the CPython packages limitation and errors.
For IronPython I had to downgrade to Python 2.7 in order to work with it. For the rest of the examples I am using Python 3.7
ScriptEngine pythonEngine = Python.CreateEngine();
var searchPaths = pythonEngine.GetSearchPaths();
searchPaths.Add(#"C:\Python27\Lib");
searchPaths.Add(#"C:\Users\Dobromir\PycharmProjects\pythonProject\venv\Lib\site-packages");
pythonEngine.SetSearchPaths(searchPaths);
List<String> argv = new List<String>();
argv.Add("Some Value1");
argv.Add("Some Value2");
ScriptSource pythonScript = pythonEngine.CreateScriptSourceFromFile("C:\\Users\\Dobromir\\PycharmProjects\\pythonProject\\main.py");
pythonEngine.GetSysModule().SetVariable("argv", argv);
pythonEngine.SetSearchPaths(searchPaths);
ScriptScope scope = pythonEngine.CreateScope();
pythonScript.Execute(scope);
Implementation of pythonnet
The issue that I got here is on the line using Py.GIL(). I believe it is having trouble finding the python files, and also tried giving the python37.dll in the variable pathToPython.
I received the error that Python.Runtime, Version=2.5.2.0, Culture=neutral....missmatch"
string pathToPython = #"C:\Users\Dobromir\AppData\Local\Programs\Python\Python37";
string path = pathToPython + "; " + Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine);
Environment.SetEnvironmentVariable("PATH", path, EnvironmentVariableTarget.Process);
Environment.SetEnvironmentVariable("PYTHONHOME", pathToPython, EnvironmentVariableTarget.Process);
Console.WriteLine(path);
var lib = new[]
{
#"C:\\Users\\Dobromir\\PycharmProjects\\App37\\main.py",
Path.Combine(pathToPython, "Lib"),
Path.Combine(pathToPython, "DLLs")
};
string paths = string.Join("; ", lib);
Environment.SetEnvironmentVariable("PYTHONPATH", paths, EnvironmentVariableTarget.Process);
using (Py.GIL()) //Initialize the Python engine and acquire the interpreter lock
{
try
{
Console.WriteLine("I am working");
}
catch (PythonException error)
{
Console.WriteLine("Error occured: ", error.Message);
}
}
I Also tried creating a bash script to execute the python script and got the no module found error as well
I know that these are not the best implementations out there, but do the job.
My question is if someone has any idea on how to make this simple operation work I would be very grateful, thank you for your time and understanding
P.S - Apologies for the long post, wanted to write what I have tried before asking for help, but if someone is more interested I will provide additional information.
I did a project like this recently; a couple of things I would suggest to make it easy:
Confirm that the instance of python set in your env variables (WIN+R, sysdm.cpl, Advanced, env variables) is that of the instance of python you wish to use (do this for your python search path too!)
Remove any lines attempting to set these in code; and instead handle errors if they are not found
Then, when you call you script from within your program; it only needs to look like this:
var processStartInfo = new ProcessStartInfo
{
Arguments = "main.py",
FileName = "Python",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
};
Process.Start(processStartInfo);
After some struggle, I found a solution to fit my needs.
Firstly, I completely removed python 2.7 and installed back 3.10.
I tried running the script file inside the shell command line and got the same error that the modules could not be found. What I did is try to import these modules and it gave an error, specifically for bs4 that I am using packages for python 2.x instead of 3.x packages.
After futher investigation I discovered that the packages that I have for my script are treated as "local" packages, meaning I installed them from the IDE (PyCharm) and they work for that project only I guess.
I also found that to "globally" access these packages I had to install them through the command line using the pip3 install <package_name>. After doing this the problem was gone and was left with running the script from the Windows Forms.
NOTE: I did not manage to start the script using python.exe, so I used bash script for the job.
Here is my code and I hope it helps someone down the line...
Code in C#
string myApp = string.Format("{0} {1}", #"C:\testing1.sh", "Hello");
var processStartInfo = new ProcessStartInfo
{
Arguments = myApp,
FileName = "C:\\Program Files\\Git\\git-bash.exe",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = false
};
Process.Start(processStartInfo)
Code in Bash Script File
#!/bin/bash
arg1="$1"
python C:/Users/Dobromir/PycharmProjects/testing/main.py "$arg1"
Inside the Python file I am using sys.argv[] and accessing the arguments.
NOTE: Passing arguments from the bash script to the python script, in this case, you will receive 2 arguments - first one is the path to the python file and the second is the variable arg1.
Another important thing to mention is you need to have comas around the $1 - this is the property that is being send from the C# file, else it will show as empty.
Articles that were useful:
Installed BeautifulSoup but still get no module named bs4
Passing arguments to Python from Shell Script
https://unix.stackexchange.com/questions/31414/how-can-i-pass-a-command-line-argument-into-a-shell-script
https://gist.github.com/creativcoder/6df4d349447ff1416e68
Thank you to everyone who contributed and tried to help, I managed to learned new things with your suggestions!

how to read, change, and write macOS file alias from python

Is there any way to read a macOS file alias, modify its contents (particularly the target file path), and write the modified alias back out?
For example, if I have the following directory structure:
./one/file.txt
./two/file.txt
./file_alias
where file_alias resolves to ./one/file.txt. I would like to be able to programmatically, in Python, read ./file_alias, determine its path, change 'one' to 'two', and write the revised alias out, overwriting ./file_alias. Upon completion, file_alias would resolve to ./two/file.txt.
Searching I've found an answer to a related question that suggests it can't be done (#Milliway's answer to [1]), a Carbon module with no substantive documentation and a statement that its functionality has been removed [2], a partially deprecated macostools module that depends on Carbon [3], an equivalent, unanswered question (except a tentative suggestion to use PyObjC) [4], and a recently updated mac_alias package [5], but have not found a way to accomplish the task based on any of these.
The mac_alias package at first seemed interesting, but I have found no way to import the bytes needed to construct an in-memory Alias object from an existing alias file (using bytes from a binary read of the alias file produces errors) and even if I could construct an in-memory Alias record and modify it, there is no way to write it out to disk.
The machine where I want this is running 10.12.x (Sierra) and I am using the built-in python 2.7.10. I find I can actually import Carbon and macostools, and suspect Carbon.File might conceivably provide what I need, but I cannot find any documentation for it. I could upgrade to High Sierra and/or install and use Python 3.x, but those don't seem to be helpful or relevant at this stage.
I realize that the alias also contains an inode, that will be stale after such a change, but thankfully, in part due to a bug I filed and a bit of persistence back when I was with Apple, an alias resolves the path first, only falls back to the inode if the path fails to resolve, and updates the inode if the path does resolve (and the inode has changed).
Any help, suggestions, pointers appreciated.
[1] How to handle OSX Aliases in Python with os.walk()?
[2] https://docs.python.org/2/library/carbon.html
[3] https://docs.python.org/2/library/macostools.html
[4] change an alias target python
[5] https://pypi.python.org/pypi/mac_alias
Solved it, using PyObjC, despite there being almost no documentation for PyObjC. You have to carefully convert ObjectiveC interfaces for NSURL to PyObjC calls, using the techniques described in "An Introduction to PyObjC" found on this site while referring to the NSURL interfaces described here.
Code in #MagerValp's reply to this question helped figure out how to get the target of an alias. I had to work out how to create a new alias with a revised target.
Below is a test program that contains and exercises all the functionality needed. Its setup and use are documented with comments in the code.
I'm a bad person and didn't do doc strings or descriptions of inputs and return values, but I've kept all functions short and single-functioned and hopefully I've named all variables and functions sufficiently clearly that they are not needed. There's an admittedly weird combination of CamelCaps and underscore_separated variable and function names. I normally use CamelCaps for global constants and underscore_separated names for functions and variables, but in this case I wanted to keep the variables and data types referred to in the PyObjC calls, which use camelCaps, unchanged, hence the odd mix.
Be warned, the Mac Finder caches some information about aliases. So if you do a Get Info or a resolve on file_alias immediately after running this program, it will look like it didn't work, even though it did. You have to drag the one folder to the Trash and empty the Trash, and only then will a Get Info or resolve of file_alias show that it does indeed now point to ./two/file.txt. (Grumble, grumble.) Fortunately this will not impact my use of these techniques, nor will it affect most people's use, I suspect. The point of the program will normally be to replace a broken alias with a fixed one, based on the fact that some single, simple thing changed, like the folder name in this example, or the volume name in my real application for this.
Finally, the code:
#!/usr/bin/env python
# fix_alias.py
# A test program to exercise functionality for retargeting a macOS file alias (bookmark).
# Author: Larry Yaeger, 20 Feb 2018
#
# Create a file and directory hierarchy like the following:
#
# one
# file.txt
# two
# file.txt
# file_alias
#
# where one and two are folders, the file.txt files are any files really, and
# file_alias is a Mac file alias that points to ./one/file.txt. Then run this program
# in the same folder as one, two, and file_alias. It will replace file_alias with
# an alias that points to ./two/file.txt.
#
# Note that file_alias is NOT a symbolic link, even though the Mac Finder sometimes
# pretends symbolic links are aliases; they are not.
import os
import string
from Foundation import *
OldFolder = 'one'
NewFolder = 'two'
AliasPath = 'file_alias'
def get_bookmarkData(alias_path):
alias_url = NSURL.fileURLWithPath_(alias_path)
bookmarkData, error = NSURL.bookmarkDataWithContentsOfURL_error_(alias_url, None)
return bookmarkData
def get_target_of_bookmarkData(bookmarkData):
if bookmarkData is None:
return None
options = NSURLBookmarkResolutionWithoutUI | NSURLBookmarkResolutionWithoutMounting
resolved_url, stale, error = \
NSURL.URLByResolvingBookmarkData_options_relativeToURL_bookmarkDataIsStale_error_(
bookmarkData, options, None, None, None)
return resolved_url.path()
def create_bookmarkData(new_path):
new_url = NSURL.fileURLWithPath_(new_path)
options = NSURLBookmarkCreationSuitableForBookmarkFile
new_bookmarkData, error = \
new_url.bookmarkDataWithOptions_includingResourceValuesForKeys_relativeToURL_error_(
options, None, None, None)
return new_bookmarkData
def create_alias(bookmarkData, alias_path):
alias_url = NSURL.fileURLWithPath_(alias_path)
options = NSURLBookmarkCreationSuitableForBookmarkFile
success, error = NSURL.writeBookmarkData_toURL_options_error_(bookmarkData, alias_url, options, None)
return success
def main():
old_bookmarkData = get_bookmarkData(AliasPath)
old_path = get_target_of_bookmarkData(old_bookmarkData)
print old_path
new_path = string.replace(old_path, OldFolder, NewFolder, 1)
new_bookmarkData = create_bookmarkData(new_path)
new_path = get_target_of_bookmarkData(new_bookmarkData)
print new_path
os.remove(AliasPath)
create_alias(new_bookmarkData, AliasPath)
main()
This thread got my interest...
But I don't think it's possible.
Look at this bug report in mac_alias: https://github.com/al45tair/mac_alias/issues/4
it notes that the package handles Alias records not Alias files. The Alias files are a 3rd version which hadn't been reverse engineered yet.
It points to this info on the Alias file: http://indiestack.com/2017/05/resolving-modern-mac-alias-files/
Also this thread on their old bitbucket: https://bitbucket.org/al45tair/mac_alias/issues/3/support-for-version-3-aliases
which points this dead page (thanks, archive.org) https://web.archive.org/web/20170222235430/http://sysforensics.org/2016/08/mac-alias-data-objects/
and info that reading some information is possible via this package: https://pypi.python.org/pypi/plistutils/ which has a bunch of docs on reading alias structures on their github
none of this does what you want though. sorry.

PyInstaller: Executable to access user-specified Sourcecode

I'm using pyinstaller to distribute my code as executable within my team as most of them are not coding/scripting people and do not have Python Interpreter installed.
For some advanced usage of my tool, I want to make it possible for the user to implement a small custom function to adjust functionality slightly (for the few experienced people). Hence I want to let them input a python file which defines a function with a fixed name and a string as return.
Is that possible?
I mean the py-file could be drag/dropped for example, and I'd tell them that their user-defined function needs to have a certain name, e.g. "analyze()" - is it now possible to import that from the drag/dropped pythonfile within my PyInstaller Script and use it as this?
I know, it certainly will not be safe/secure and they could do evil things, delete files and so one... But that are things which we don#t care at this point, please no discussions about it. Thanks!
To answer my own question: yes it does actually work to import a module/function from a given path/pythonfile at runtime (that I knew already) even in PyInstaller (that was new for me).
I used this for my Py2.7 program:
f = r'C:\path\to\userdefined\filewithfunction.py'
if os.path.exists(f):
import imp
userdefined = imp.load_source('', f) # Only Python 2.x, for 3.x see: https://stackoverflow.com/a/67692/701049
print userdefined # just a debugging print
userdefined.imported() # here you should use try/catch; or check whether the function with the desired name really exists in the object "userdefined". This is only a small demo as example how to import, so didnt do it here.
filewithfunction.py:
--------------------
def imported():
print 'yes it worked :-)'
As written in the comments of the example code, you'll need a slightly different approach in Python 3.x. See this link: https://stackoverflow.com/a/67692/701049

How to convert a string into a set of keystrokes in python on OSX

Given a string, I'd like to be able to send a set of keystrokes to type that string and I'd like to be able to do it in python on OSX (in python because it's part of a larger project already written in python, on OSX because I am trying to port it to OSX).
I'm able to do this now using pyobj like so (this is somewhat simplified):
from Quartz import *
CHAR_TO_SEQUENCE = {
'a':[(0, True), (0, False)]
}
def send_string(string):
for c in string:
sequence = CHAR_TO_SEQUENCE[c]
for keycode, key_down in sequence:
CGEventPost(kCGSessionEventTap, CGEventCreateKeyboardEvent(None, keycode, key_down))
and I've fleshed out CHAR_TO_SEQUENCE to include most of what I can type on my keyboard, which took a while and was tedious.
The problems with this are:
- It only works while the keyboard has the ANSII standard layout. If someone uses a french keyboard, for example, it will type the wrong things.
- It requires this ridiculous table.
I found this general solution for OSX but couldn't figure out how to apply it to python:
How to convert ASCII character to CGKeyCode?
The API mentioned there doesn't seem to be available via pyobj (or maybe I just couldn't figure out the import path).
I've seen some suggestions to set CGEventKeyboardSetUnicodeString to the desired string and not worry about the keycode. But I wasn't able to figure out how to call CGEventKeyboardSetUnicodeString from python (I can't get the arguments right) and it's not clear that this will work because the documentation says that applications can choose to ignore this in favor of the keycode.
Is there a way to do this in python on OSX?
It looks like the Carbon modules don't wrap the TIS* functions, and neither does anything else.
You could extend PyObjC, but it's much simpler to just build a trivial extension module that wraps the two functions you actually need.
Since it was faster to just do it than to explain how to do it, you can get it from https://github.com/abarnert/pykeycode and just do the usual "python setup.py build_ext --inplace" or "sudo python setup.py install", then look at test.py to see how to use it.

Categories