Skip to content

Instantly share code, notes, and snippets.

@ShalokShalom
Created May 11, 2025 08:49
Show Gist options
  • Save ShalokShalom/fce61a7bd17e4e0cc80c9292cfa43477 to your computer and use it in GitHub Desktop.
Save ShalokShalom/fce61a7bd17e4e0cc80c9292cfa43477 to your computer and use it in GitHub Desktop.
# python_julia_connector.py
import os
from juliacall import Main as jl
# from juliacall import JuliaError # To specifically catch Julia errors if needed
# --- Environment Management & Initialization ---
# You might need to configure JuliaCall if Julia is not in your PATH
# or if you want to use a specific Julia project environment.
# For example:
# from juliacall import JuliaCall # For more control
# jl = JuliaCall(julia="path/to/julia_executable") # Specify Julia executable
# jl.Pkg.activate("path/to/your/JuliaProject") # Activate a Julia project
print("Python: Initializing Julia connection via juliacall...")
try:
# Eagerly evaluate something simple to ensure Julia is working
# This also triggers Julia compilation/setup if it's the first time.
jl_version_info = jl.seval("VERSION")
# jl.VERSION is a juliacall object, convert to string for cleaner printing
jl_version_str = f"{jl_version_info.major}.{jl_version_info.minor}.{jl_version_info.patch}"
print(f"Python: Successfully connected to Julia {jl_version_str}")
except Exception as e:
print(f"Python: CRITICAL - Error initializing Julia: {e}")
print("Python: Please ensure Julia is installed and juliacall is configured correctly.")
# Re-raise to make it clear to Racket/pyffi that initialization failed.
raise
# --- Function Exposure & Julia Interaction ---
# Define some Julia functions directly for this example.
jl.seval("""
module PyConnectorJuliaModule
export greet_from_julia, add_in_julia, process_list_julia, complex_julia_computation, julia_sqrt
function greet_from_julia(name::String)
return "Hello, " * name * " from Julia! 👋 (via PyConnectorJuliaModule)"
end
function add_in_julia(a::Number, b::Number)
return a + b
end
function process_list_julia(arr::Vector)
return isempty(arr) ? 0.0 : sum(x^2 for x in arr)
end
function complex_julia_computation(data::Dict)
if haskey(data, "value") && haskey(data, "multiplier")
val = data["value"]
mult = data["multiplier"]
if !(val isa Number && mult isa Number)
return "Error: 'value' and 'multiplier' must be numbers."
end
return val * mult
else
return "Error: Missing 'value' or 'multiplier' in Dict."
end
end
julia_sqrt(x::Float64) = sqrt(x) # Simple function for eval_julia_string later
end
""")
# Make the module's functions accessible in Python via jl.ModuleName
jl.seval("using .PyConnectorJuliaModule")
print("Python: PyConnectorJuliaModule loaded and its functions are available.")
# --- Python functions callable from Racket (Abstraction Layer) ---
def call_julia_greet(name_str):
"""Calls the greet_from_julia function in Julia."""
print(f"Python: call_julia_greet received '{name_str}'")
try:
result = jl.PyConnectorJuliaModule.greet_from_julia(name_str)
print(f"Python: Julia returned: '{result}'")
return result
except Exception as e:
print(f"Python: Error in call_julia_greet: {e}")
return f"Python Error: {e}"
def call_julia_add(a, b):
"""Calls the add_in_julia function in Julia."""
print(f"Python: call_julia_add received {a}, {b}")
try:
result = jl.PyConnectorJuliaModule.add_in_julia(a, b)
print(f"Python: Julia returned: {result}")
return result
except Exception as e:
print(f"Python: Error in call_julia_add: {e}")
raise
def call_julia_process_list(py_list):
"""Calls process_list_julia with a list."""
print(f"Python: call_julia_process_list received {py_list}")
try:
result = jl.PyConnectorJuliaModule.process_list_julia(py_list)
print(f"Python: Julia returned: {result}")
return result
except Exception as e:
print(f"Python: Error in call_julia_process_list: {e}")
raise
def call_julia_complex_op(py_dict):
"""Calls complex_julia_computation with a dictionary."""
print(f"Python: call_julia_complex_op received {py_dict}")
try:
result = jl.PyConnectorJuliaModule.complex_julia_computation(py_dict)
print(f"Python: Julia returned: {result}")
return result
except Exception as e:
print(f"Python: Error in call_julia_complex_op: {e}")
raise
def eval_julia_string(julia_code_string):
"""Evaluates an arbitrary string of Julia code in the Main Julia module."""
print(f"Python: eval_julia_string received: '{julia_code_string}'")
try:
# jl.seval evaluates in Julia's Main scope.
# If functions from PyConnectorJuliaModule are needed, they are already `using`ed.
result = jl.seval(julia_code_string)
# juliacall might return specific Julia types (e.g. JuliaObject).
# For simple results, it often converts them to Python types.
# If result is a complex Julia object, you might need jl.pyconvert(result)
print(f"Python: Julia eval returned: {result} (type: {type(result)})")
return result
except Exception as e:
print(f"Python: Error evaluating Julia string: {e}")
# Return a string indicating error, or re-raise
return f"Python Error during Julia eval: {e}"
if __name__ == '__main__':
# For testing the Python script directly
print("\n--- Testing Python script directly ---")
print(f"Greet: {call_julia_greet('Direct Python Test')}")
print(f"Add: {call_julia_add(10, 25)}")
print(f"Process List: {call_julia_process_list([1, 2, 3, 4])}")
print(f"Complex Op: {call_julia_complex_op({'value': 5, 'multiplier': 3, 'extra_key': 'test'})}")
print(f"Eval String (sqrt): {eval_julia_string('PyConnectorJuliaModule.julia_sqrt(16.0)')}")
print(f"Eval String (mean): {eval_julia_string('using Statistics; mean([10.0, 20.0, 30.0])')}")
print("--- End direct Python test ---")
#lang racket/base
(require pyffi
racket/file
racket/pretty
racket/match
racket/format)
(printf "Racket: Starting Julia bridge script...\n")
;; --- Configuration (Optional: Customize Python Executable) ---
;; If your Python executable (especially from a virtual environment)
;; is not in the default PATH, or you want to be explicit:
;; (pyffi-option-set 'python-executable "/path/to/your/venv/bin/python")
;; Example for a venv in the current project directory:
;; (define venv-python-path (build-path (current-directory) ".venv" "bin" "python"))
;; (when (file-exists? venv-python-path)
;; (printf "Racket: Using Python from ~s\n" venv-python-path)
;; (pyffi-option-set 'python-executable venv-python-path))
;; --- Path to the Python Connector Script ---
(define python-connector-filename "python_julia_connector.py")
(define python-connector-path (build-path (current-directory) python-connector-filename))
(unless (file-exists? python-connector-path)
(eprintf "Racket: CRITICAL - Python connector script not found: ~a\n" python-connector-path)
(eprintf "Racket: Please ensure '~a' is in the same directory as this Racket script.\n" python-connector-filename)
(exit 1))
(printf "Racket: Found Python connector at ~s\n" python-connector-path)
;; --- Importing the Python module ---
;; This will execute python_julia_connector.py.
;; Output from Python's print statements during its initialization will appear here.
(printf "Racket: Attempting to import Python module via pyffi...\n")
(define pyjuliaconnector
(try
(python-import python-connector-path) ; pyffi imports the file as a module
(handle [exn:fail:pyffi? exn]
(eprintf "Racket: CRITICAL - pyffi failed to import or initialize the Python module.\n")
(eprintf "Racket: Python error: ~a\n" (exn-message exn))
(eprintf "Racket: Check Python console output above for details from 'python_julia_connector.py'.\n")
(eprintf "Racket: Ensure Python, Julia, and juliacall are correctly installed and configured.\n")
(exit 1))))
(printf "Racket: Python module imported successfully as 'pyjuliaconnector'.\n\n")
;; --- Racket Functions to Interact with Julia via Python ---
(define (julia-greet name)
(printf "Racket: Calling julia-greet with \"~a\"\n" name)
(handle-python-call
(lambda () (pyjuliaconnector call_julia_greet name))
'julia-greet))
(define (julia-add a b)
(printf "Racket: Calling julia-add with ~a and ~a\n" a b)
(handle-python-call
(lambda () (pyjuliaconnector call_julia_add a b))
'julia-add))
(define (julia-process-list rkt-list)
(printf "Racket: Calling julia-process-list with ~s\n" rkt-list)
(handle-python-call
(lambda () (pyjuliaconnector call_julia_process_list rkt-list))
'julia-process-list))
(define (julia-complex-op rkt-hash)
(printf "Racket: Calling julia-complex-op with ~s\n" rkt-hash)
(handle-python-call
(lambda () (pyjuliaconnector call_julia_complex_op rkt-hash))
'julia-complex-op))
(define (julia-eval-string julia-code)
(printf "Racket: Calling julia-eval-string with \"~a\"\n" julia-code)
(handle-python-call
(lambda () (pyjuliaconnector eval_julia_string julia-code))
'julia-eval-string))
;; Helper for handling pyffi exceptions
(define (handle-python-call thunk call-name)
(with-handlers ([exn:fail:pyffi?
(lambda (exn)
(eprintf "Racket: Error during '~a' call to Python/Julia.\n" call-name)
(eprintf " Python/Julia error: ~a\n" (exn-message exn))
(values #f (exn-message exn)))]) ; Return #f and error message
(let ([result (thunk)])
(printf "Racket: '~a' successful. Received: ~s\n" call-name result)
(values result #f)))) ; Return result and no error
;; --- Demonstrating the Bridge ---
(printf "\n--- Racket: Demonstrating Julia Calls via Python Bridge ---\n")
(let-values ([(result err) (julia-greet "Racket User")])
(if err
(printf "Racket Greet Error: ~a\n" err)
(printf "Racket Greet Result: ~s\n\n" result)))
(let-values ([(result err) (
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment