Skip to content

Commit 07f3af0

Browse files
authored
Merge pull request #75 from ian-coccimiglio/fix-python-imports
Fix global imports in parsed code
2 parents d1583a1 + e02e167 commit 07f3af0

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

src/scyjava/_script.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ def apply(self, arg):
9090
):
9191
# Last statement looks like an expression. Evaluate!
9292
last = ast.Expression(block.body.pop().value)
93-
94-
_globals = {}
93+
# See here for why this implementation: https://docs.python.org/3/library/functions.html#exec
94+
# When `exec` gets two separate objects as *globals* and *locals*, the code will be executed as if it were embedded in a class definition.
95+
# This means functions and classes defined in the executed code will not be able to access variables assigned at the top level
96+
# (as the “top level” variables are treated as class variables in a class definition).
97+
_globals = script_locals
9598
exec(
9699
compile(block, "<string>", mode="exec"), _globals, script_locals
97100
)

tests/it/script_scope.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""
2+
Test the enable_python_scripting function, but here explictly testing import scope for declared functions.
3+
"""
4+
5+
import sys
6+
7+
import scyjava
8+
9+
scyjava.config.endpoints.extend(
10+
["org.scijava:scijava-common:2.94.2", "org.scijava:scripting-python:MANAGED"]
11+
)
12+
13+
# Create minimal SciJava context with a ScriptService.
14+
Context = scyjava.jimport("org.scijava.Context")
15+
ScriptService = scyjava.jimport("org.scijava.script.ScriptService")
16+
# HACK: Avoid "[ERROR] Cannot create plugin" spam.
17+
WidgetService = scyjava.jimport("org.scijava.widget.WidgetService")
18+
ctx = Context(ScriptService, WidgetService)
19+
20+
# Enable the Python script language.
21+
scyjava.enable_python_scripting(ctx)
22+
23+
# Assert that the Python script language is available.
24+
ss = ctx.service("org.scijava.script.ScriptService")
25+
lang = ss.getLanguageByName("Python")
26+
assert lang is not None and "Python" in lang.getNames()
27+
28+
# Construct a script.
29+
script = """
30+
#@ int age
31+
#@output String cbrt_age
32+
import numpy as np
33+
import math
34+
35+
def calculate_cbrt(age):
36+
return round(math.cbrt(age))
37+
38+
cbrt_age = calculate_cbrt(age)
39+
# cbrt_age = round(math.cbrt(age))
40+
f"The rounded cube root of my age is {cbrt_age}"
41+
"""
42+
StringReader = scyjava.jimport("java.io.StringReader")
43+
ScriptInfo = scyjava.jimport("org.scijava.script.ScriptInfo")
44+
info = ScriptInfo(ctx, "script.py", StringReader(script))
45+
info.setLanguage(lang)
46+
47+
# Run the script.
48+
future = ss.run(info, True, "age", 13)
49+
try:
50+
module = future.get()
51+
outputs = module.getOutputs()
52+
statement = outputs["cbrt_age"]
53+
return_value = module.getReturnValue()
54+
except Exception as e:
55+
sys.stderr.write("-- SCRIPT EXECUTION FAILED --\n")
56+
trace = scyjava.jstacktrace(e)
57+
if trace:
58+
sys.stderr.write(f"{trace}\n")
59+
raise e
60+
61+
assert statement == "2"
62+
assert return_value == "The rounded cube root of my age is 2"

0 commit comments

Comments
 (0)