Working with Python source files

Using Python snippets to enter and execute Python code is the easiest and most user friendly way to get started with the Python Bridge.

For Python developers the next step is to learn how to work with traditional Python source files.

There are several ways to do this but it is important to understand the architecture of the Python Bridge first.

Fundamental architecture

The Python Bridge executes a Python kernel running in a pipenv virtual environment in a specific directory. It is assumed that the gtoolkit_bridge PyPI module is installed in this environment.

This directory can be located anywhere and the installation and lifecyle of gtoolkit_bridge can be controlled freely. There are other documents describing these advanced options.

The default is to use the PythonBridgeRuntime directory next to the GT image and to have GT control the lifecyle.

Let's start the global default PBApplication that is used by all Python snippets. This happens automatically when a Python snippet is first used, at which point all Python snippets are connected to the same Python kernel.

PBApplication start
  

We can now explore the fundamentals of running Python kernel: its working directory, virtual environment, installed modules and system source code load paths. GT inspector views show this information as well (follow the flow PBApplication > Internals > Process handler > Process).

PBApplication uniqueInstance workingDirectory
  
PBApplication uniqueInstance processHandler process installedVirtualEnvironment
  
PBApplication uniqueInstance processHandler process installedModules
  
PBApplication uniqueInstance processHandler process installLog
  
import sys
sys.path
  

Hello world

Let's add a hello world Python source file into our working directory. First we assign the helloWorldPy variable to a string of Python source code. Then we copy that into a hello_world.py file.

helloWorldPy := '# hello_world.py
def greeting(message="Hello World!"):
	print(message)
	return message'
  
PBApplication uniqueInstance workingDirectory / 'hello_world.py'
	writeStreamDo: [ :out |
		out nextPutAll: helloWorldPy ]
  

With the new source file written, we can use it like any other Python source file with the filename being the module name.

import hello_world
hello_world.greeting()
  

Notice how the greeting() function both returns the message, so that we have a function result, but also outputs it. Inspect the runtime or Python bridge application and select the Stdout tab to view the output of the print() statement.

Alternatively, we can programmatically evaluate the source. This is almost the same, except that this evaluation occurs in the scope gtoolkit_bridge.PythonBridge.python_bridge, which is fine and transparant as long as you keep on using Python snippets.

PBApplication do: [ :application | 
	application newCommandStringFactory 
		script: helloWorldPy; 
		send ]
  
# no need to import or use qualified names
# we remain in gtoolkit_bridge.PythonBridge.python_bridge
greeting()
  

Viewing and editing Python source files

Now that our working directory is non-empty, we can open an LSP IDE on it to view and/or edit our source files. Click the edit GT action button in the top right when inspecting the current PBApplication or start it manually. Click the + icon in the top left to open the directory view and select another file. Use the - icon to get back.

GtLSPPythonModel onDirectory: PBApplication uniqueInstance workingDirectory
  

Notice how we get proper syntax highlighting as well as completion (provided you install the pyright command line tuility).

Working with source trees

When working with a larger source tree you can still copy over files, but that is not the optimal solution.

The better solution is to use the source tree as it is, either by extending Python's code load path, or by creating a symbolic link in the working directory.

Let's create another example, based on the following source code bound to nowPy and place it inside a trivial source tree in a temporary directory.

nowPy := 'from datetime import datetime
def now():
	return str(datetime.now())'
  
location := FileLocator temp / 'src' / 'my_module'.
location ensureCreateDirectory.
location / 'my_file.py'
	writeStreamDo: [ :out |
		out nextPutAll: nowPy ]
  
sourceTree := location parent
  

Now we can add our base source directory to Python's code search path and then use it.

sourceTreePath := sourceTree fullName
  
import sys
sys.path.append(sourceTreePath)
  
# invoke my_module.my_file.now()
from my_module import my_file
my_file.now()
  

To reload a changed/edited file after an import into a running Python kernel, you can do.

import my_module
import importlib
importlib.reload(my_module.my_file)
  

Another solution is to create a symlink inside the working directory to our source tree.

source := PBApplication uniqueInstance workingDirectory fullName.
target := location fullName.
LibC runCommand: 'cd ', source , ' && ln -s ', target 
  

Make sure to first stop the PythonBridge if you tried the earlier path extension approach.

Working on the Python Bridge sources

A special case occurs when working on the source code of the Python Bridge itself, or on the examples included therein.

This is actually similar to working with a source tree, with the best solution being a sumbolic link to the actual sources in their checked out git repository.

Follow PBApplication > Internals > Process handler > Process and select the + GT action button to create a symlink in the working directory to the the normal place where the sources are located.

FileLocator gtResource / 'feenkcom' / 'PythonBridge' / 'PyPI' / 'src' / 'gtoolkit_bridge'
  
PBApplication uniqueInstance processHandler process linkGToolkitBridgePyPISources
  

Now you can use the IDE like before. Note that for any changes to take effect, already loaded or used files need to be reloaded. When running classes are changed you need to restart the Python Bridge itself.