calling load_script or user_script_load in another session or users script: globals are not updated
Description of the original "issue"
It is not really an issue but a misunderstanding of the globals()
inside the session and user scripts.
Session setup script: its globals IS the session env_dict
# demo_session_setup.py
load_script("demo_session.py")
MyTomoClass
print("'MyTomoClass' from user script loaded in demo_session.py is available in the session")
Session script: its globals is a COPY of the session env_dict
# demo_session.py
from bliss.setup_globals import * # currently a redundant statement
from bliss import current_session
user_script_homedir("/home/denolf/dev/bliss/demo/userscripts")
user_script_load("tomo.py", export_global=True) # this could also be `load_script`, same behavior
try:
MyTomoClass
except NameError:
print("'MyTomoClass' from user script not defined in this script as expected")
else:
raise RuntimeError("'MyTomoClass' is not expected to be defined in this script")
assert "MyTomoClass" in current_session.env_dict # checks that user_script_load did its job
User script: its globals is a COPY of the session env_dict
# .../demo/userscripts/tomo.py
class MyTomoClass:
pass
So calling load_script
or user_script_load
inside another session or users script (except for the session setup script) has the behavior that the globals of the parent script are not updated by loading the child script. The session env_dict
is updated as expected.
Code analysis
Analysis of the current script loading in the bliss.common.session.Session
class
Session.setup:
call self._load_config
copy session attributes to session env_dict
call _setup of child sessions
call self._setup
Session._load_config:
yaml to session env_dict
Session._setup:
globals of script exec: session env_dict
copy globals after exec to
1. setup_globals
no restrictions
Session.load_script:
globals of script exec: session env_dict.copy()
copy globals after exec to
1. self.env_dict
skip _*
Session._user_script_exec:
globals of script exec: session env_dict.copy()
copy globals after exec to
1. no copy
2. self.env_dict
skip _*
overwrite keys with warning
3. existing namespace `export_global` in self.env_dict
skip _*
must be in c_code.co_names
overwrite attributes silently
4. non-existing namespace `export_global` in self.env_dict
skip _*
must be in c_code.co_names
overwrite self.env_dict[export_global] with warning
5. return
skip _*
must be in co_names
Points of discussion
-
1. Currently bliss.setup_globals
contains a copy of the sessionenv_dict
at the end ofSession.setup
. So this is a frozen snapshot ofenv_dict
after setup. External projects that need access to the sessionsenv_dict
should access it directly throughcurrent_session
. It is unclear why they need access to the frozen state ofenv_dict
after setup. -
2. For loading the user scripts we do not use _StringImporter
but it is used for the session scripts. A complicated mechanism with an unclear goal. -
3. For user scripts we do some weird stuff getattr(self.env_dict.get(export_global), "__module__", None) == "bliss.common.utils.namespace"
-
4. When populating session env_dict
or user name spaces we sometimes filter onc_code.co_names
in addition to filtering on private names (starting with underscore) and sometimes we don't. I guess this is only for optimization? Because some names in the globals of the script come from the globals passed by theexec(c_code, globals_dict)
call. Shouldn't this be done everywhere? Or maybe there are other subtle differences? -
5. Imported modules in the loaded scripts also make their way in the session env_dict
(or the user namespace). Is that a good thing? What if someone doesfrom numpy import *
? -
6. We should remove the usage of bliss.setup_globals
from the core library in favor ofcurrent_session.env_dict