Forum Archive

Emulate run button - run script from another script

zrzka

Hi all,

did anyone know how to run a script from another script in the same way as Pythonista does? I mean, emulate Run button behavior?

First option is - URL scheme - but there's one drawback - This only works if no script is already running when the app is started.

Another option is runpy.run_path. But it looks like that there's some problem with globals, modules, paths, ... When I tried to bind Cmd Shift S ...

def launch_stash():
    blackmamba.ide.run_script(os.path.expanduser('~/Documents/launch_stash.py'))


register_key_command(
    'S',
    UIKeyModifierCommand | UIKeyModifierShift,
    launch_stash,
    'Launch StaSH...')

... to launch StaSh, it fails with No module named ConfigParser. My run_script looks like:

def run_script(file_path):
    runpy.run_path(file_path, run_name='__main__')

What's the recommended way to launch script from a script in the same way as Pythonista does with the Play button?

Thanks,
Zrzka

omz

Perhaps a Python 2/3 issue? The ConfigParser module has been renamed to configparser (lowercase) in Python 3. I'd say that runpy is the way to go.

dgelessus

I don't know how to exactly emulate Pythonista's script running, but runpy should be pretty close. The only difference is that it doesn't clean the environment, but that shouldn't be an issue for most scripts.

As @omz said, the reason why Stash isn't working is probably because you're trying to run it with Python 3, but it only works on Python 2.

zrzka

Thanks to both of you. I realized this immediately after I did post this question. Anyway, it makes things little bit harder, because it forces me to make Black Mamba Python 2/3 compatible. But it will not probably solve my issue anyway, because I can't run Python 2 scripts from Python 3 and vice versa. Would be nice if editor (or any other module) has function like run_script which can issue runpy.run_path under correct interpreter, based on shebang line.

omz

I think @JonB (or someone else?) had success using the Python C API via ctypes for this kind of thing, not sure…

zrzka

If anyone wonders why I need this, check this comment in another thread. This is the last missing piece to make me happy :)

zrzka

I solved it with URL scheme for now, but it's kinda limited. Anyway I'm interested in this Python C API hack to choose interpreter, ...

JonB

This thread might be useful. Things have changed a bit in recent versions, so i update my original discoveries below:

If you are trying to emulate play button, it eventually calls:

I3=ObjCClass('PYK3Interpreter').sharedInterpreter()
I2=ObjCClass('PYK2Interpreter').sharedInterpreter()
#pick which interpreter to run (I2 vs I3) and whether to reset environment
I2.runScriptAtPath_argv_resetEnvironment_('/private/var/mobile/Containers/Shared/AppGroup/C534C622-2FDA-41F7-AE91-E3AAFE5FFC6B/Pythonista3/Documents/Untitled_150.py',['1', 'hello'], True)

(Wrench menu calls the same selector, just with resetEnvironment=False, and populating first argv with editor script name )

I believe there might be a limitation that if a script is running already, it might be ignored, but i am not sure if that is still true. Polling for running() to return 0 would work, if that was the case.

Previously, a script had to be open in the editor, but that is no longer the case it seems.

For running python commands directly via the cpythonapi, see the above thread.

JonB

btw, if you go the runpy/cpythonapi route to try to run concurrently,

ObjCClass('PA3InterpreterWrapper').shebangVersionForScript_()

might be helpful to tell you which version to run. Then load proper cpythonapi (see the stash thread), then to emulate pythonista evironment reset, you could run the preflight script:

import os
version =2 #or 3
preflight_script=os.path.join(os.path.split(os.__file__)[0],'../../Py{}Kit.framework/pykit_preflight.py'.format(version))
omz

@JonB said:

I believe there might be a limitation that if a script is running already, it might be ignored, but i am not sure if that is still true. Polling for running() to return 0 would work, if that was the case.

You could in principle use the other interpreter like this, but trying to run a script in the current interpreter with this high-level API isn't possible.

zrzka

@JonB thanks! Will try this.

omz

@zrzka I don't think you'll get very far with this approach, to be honest.

zrzka

@omz learn or die :) At least I'll learn more about Pythonista internals.