Shortcut to run a script located in Working Copy's dir?
This seems like it should be a simple thing, but after searching through these forums, Googling, and asking ChatGPT, I still came up short.
I'm using Pythonista to run code that's managed by Working Copy, so it's under that app's directory. I'm trying to create a shortcut on the home screen to directly run a script in that directory.
It seems like there's a couple options here- a
pythonista3://URL opened in Safari with
?action=run, or running Pythonista directly using the Shortcuts app.
I've found I can get the full directory path with
os.path.realpath(__file__), but everything I try just results in Pythonista opening with no scripts open (if I use the URL), or with the last used script open (if I use the Run Script shortcut). It doesn't try to run anything. When I tried it a couple weeks ago, it would give me error messages about the script not being found, but now I'm not even getting those.
ChatGPT suggested using the Wrench in Pythonista, but it gives me the error message "Script Required: You need to open a Python script in order to generate URLs." A script is already open in the editor, so I'm guessing this only works if the script is located in Pythonista's directory.
I'm guessing this only works if the script is located in Pythonista's directory.
I think that's true. Perhaps find a way to start a Pythonista script which it-self starts the script of Working Copy.
In Pythonista V3.3 we were able to start a script running in Python 2 which started a Python 3 interpreter like a wrench script does.
#!python2 # force this script to use Python 2 Interpreter so you can start # Python 3 Interpreter to run your iCloud script from objc_util import * import sys arg = sys.argv # this script = arg script = arg path = '/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/' + script arg = arg[2:] # other arguments I3=ObjCClass('PYK3Interpreter').sharedInterpreter() print(I3) # run a script like in wrench menu (path, args, reset env yes/no) #print(path,script) I3.runScriptAtPath_argv_resetEnvironment_(path, arg, True)
@bensaccount as we can't have two times the Pythonista Python 3 interpreter at the same time, I have tested with another thread. Here, an example with (I don't use Working Copy) starting via runScript another script in iCloud
import sys import threading class my_thread_(threading.Thread): def __init__(self,path,arg): threading.Thread.__init__(self) self.path = path self.arg = arg @on_main_thread def run(self): from objc_util import ObjCClass from time import sleep sleep(1) I3 = ObjCClass('PYK3Interpreter').sharedInterpreter() # run a script like in wrench menu (path, args, reset env yes/no) #print(path,script) I3.runScriptAtPath_argv_resetEnvironment_(self.path, self.arg, True) arg = sys.argv # this script = arg script = arg path = '/private/var/mobile/Library/Mobile Documents/iCloud~com~omz-software~Pythonista3/Documents/' + script arg = arg[2:] # other arguments my_thread = my_thread_(path,arg) my_thread.start()
Execute this shortcut
@cvp Thanks, having a launcher script in Pythonista's Documents dir fixes the issue! I think starting another interpreter may be unnecessary though, since I do want the script to run inside Pythonista, and using the same version of Python.
Here's what I ended up using for my launcher script:
dir = '/private/var/mobile/Containers/Shared/AppGroup/......' filename = 'app.py' import sys import os sys.path.append(dir) os.chdir(dir) with open(filename) as f: exec(f.read())
This seems to work fine. And of course this could be altered to take arguments for directory and filename.
I've run into a couple issues with Shortcuts now:
- If I use the Pythonista Run Script shortcut, I have to stop the shortcut manually afterwards, even if Pythonista is closed. Adding "Stop this shortcut" doesn't make a difference. It's not a huge deal, but it may be confusing for the client.
- Pythonista will happily run the script multiple times. Is there any way to tell Pythonista not to do this, or do I need to implement something manually, like a lock file? (Or is there a more reliable way to check if a script is running?)
edit: Ah, I've found TPO's AppSingleLaunch util. Looks like it handles the issues with lock files sticking around when the app doesn't shut down properly, so I may try that.
@bensaccount I didn't know this exec function.... Thanks for the info.
@bensaccount I have tried successfully a shortcut with unique action
@cvp That's handy. So a separate launcher script file isn't needed.
It can still launch multiple times though. I think I'll have to add TPO's util to my app to prevent that.
I'm running into two problems with @TPO's AppSingleLaunch:
- I'm using a NavigationView as my top-level view, which doesn't have a will_close function. It's immutable, so it won't let me monkey patch one in either. Is there another way to detect when the script is exiting?
- I don't think the forced garbage collection is working. If I close the Python script and re-open it (which doesn't delete the lock file due to issue #1), the id it stores- the NavigationView's id- still exists. Even if I wait a while, gc.collect() won't collect it.
Hm, if I add a
will_closemethod to my root View, it never gets called, I guess that's only called when you use
The UIKit docs show a
viewWillDisappearmethod for NavigationView, which does show up in
__dir()__for its objc object. Is there any way to set that via Python?
My temporary workaround is
hide_close_button=True, so the app can only shutdown properly. But there's still the problem I mentioned earlier of AppSingleLaunch not working due to the NavigationView not getting cleaned up after closing. (edit: Though it's not a problem if the script always closes down properly)
@bensaccount try to put a close button to top view of NavigationView
import ui view = ui.View() navigationView = ui.NavigationView(view) # Add a close button view.left_button_items = [ui.ButtonItem(title="Close", action=lambda x: navigationView.close())] # Won't show the x-button navigationView.present("sheet", hide_title_bar=True)
@cvp Thanks, looks like we had the same idea. I had just removed the close button, but I have to admit that hiding the title bar is tempting to reclaim some vertical space.
@bensaccount so you can intercept the root view closing, like a will_close
If it is not sufficient, I think that, in Objective C, we could intercept the delegate should_pop of the UINavigationBar, but more complex of course