From c6f07396a9ff3a50e98054a188510b0b81e67714 Mon Sep 17 00:00:00 2001 From: Capocchi Date: Tue, 12 Dec 2023 10:31:16 +0100 Subject: [PATCH 1/2] update --- StandaloneGUI.py | 53 ++++++++++++++++++++++++++++++++++++++++------ StandaloneNoGUI.py | 44 ++++++++++++++++++++++++++++++++++---- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/StandaloneGUI.py b/StandaloneGUI.py index 906a3f8d..359056bb 100644 --- a/StandaloneGUI.py +++ b/StandaloneGUI.py @@ -93,6 +93,9 @@ class StandaloneGUI(wx.Frame): Args: wx (wx.Frame): GUI to configure the exportation of the zip file. """ + + last_opened_directory = "." # Class variable to store the last opened directory + def __init__(self, *args, **kw): """Constructor. """ @@ -113,7 +116,7 @@ def InitUI(self): icon.CopyFromBitmap(wx.Bitmap(os.path.join(ICON_PATH_16_16, "properties.png"), wx.BITMAP_TYPE_ANY)) self.SetIcon(icon) - self.SetSize((-1, 220)) + self.SetSize((-1, 250)) panel = wx.Panel(self) vbox = wx.BoxSizer(wx.VERTICAL) @@ -121,9 +124,11 @@ def InitUI(self): ### Zip name field hbox1 = wx.BoxSizer(wx.HORIZONTAL) self.st1 = wx.StaticText(panel, label=_('Filename:')) + self.st1.SetToolTip(_("Select a name for the standalone package")) hbox1.Add(self.st1, flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, border=8) self._tc = wx.TextCtrl(panel, -1, "devsimpy-nogui-pkg.zip", style=wx.TE_RICH2|wx.BORDER_NONE, validator=ZipNameValidator()) + self._tc.SetToolTip(_("Must end with .zip")) hbox1.Add(self._tc, proportion=1) vbox.Add(hbox1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10) @@ -135,17 +140,39 @@ def InitUI(self): ### Zip directory field hbox2 = wx.BoxSizer(wx.HORIZONTAL) - self._dbb = filebrowse.DirBrowseButton(panel, -1, size=(450, -1)) + self._dbb = filebrowse.DirBrowseButton(panel, + -1, + size=(450, -1), + labelText = "Select Directory:", + toolTip=_("Select a target directory to create the standalone package")) + # Set the initial directory + self._dbb.startDirectory = StandaloneGUI.last_opened_directory + hbox2.Add(self._dbb) vbox.Add(hbox2, flag=wx.LEFT | wx.TOP, border=10) + vbox.Add((-1, 10)) + + ### minimal (optimal) or full export + hbox3 = wx.BoxSizer(wx.HORIZONTAL) + label_format = wx.StaticText(panel, -1, _("Format:")) + label_format.SetToolTip("""The minimal format includes in the archive only the necessary library files\n (may lead to errors depending on your model's architecture).\n The full format includes all library dependencies (less optimal but more secure).""") + self.format = wx.Choice(panel, -1, choices=["Minimal", "Full"]) + self.format.SetSelection(0) + + hbox3.Add(label_format, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL) + hbox3.Add(self.format, flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=10) + vbox.Add(hbox3, flag=wx.LEFT, border=10) vbox.Add((-1, 10)) ### Options hbox4 = wx.BoxSizer(wx.HORIZONTAL) self._cb1 = wx.CheckBox(panel, label=_('Add Simulation Kernel')) + self._cb1.SetToolTip(_("Add all files needed to simulate the model.")) self._cb2 = wx.CheckBox(panel, label=_('Add Docker File')) - self._cb3 = wx.CheckBox(panel, label=_('No Time Limit')) + self._cb2.SetToolTip(_("Add Docker file to execute the simulation in a nogui model in a Docker Container.")) + self._cb3 = wx.CheckBox(panel, label=_('Add NTL Flag')) + self._cb3.SetToolTip(_("Add the flag -ntl to the simulation in order to define an infinit simulation loop.")) hbox4.Add(self._cb1) hbox4.Add(self._cb2, flag=wx.LEFT, border=10) @@ -156,6 +183,7 @@ def InitUI(self): hbox5 = wx.BoxSizer(wx.HORIZONTAL) self._cb4 = wx.CheckBox(panel, label=_('Real Time')) + self._cb4.Enable(False) self.kernel = wx.Choice(panel, -1, choices=["PyDEVS", "PyPDEVS"]) self.kernel.SetSelection(0) self.kernel.Enable(False) @@ -184,14 +212,26 @@ def InitUI(self): ### Binds self.Bind(wx.EVT_BUTTON, self.OnOk, id=btn1.GetId()) self.Bind(wx.EVT_BUTTON, self.OnClose, id=btn2.GetId()) - self.Bind(wx.EVT_CHECKBOX,self.onChecked, id=self._cb1.GetId()) + self.Bind(wx.EVT_CHECKBOX,self.OnChecked, id=self._cb1.GetId()) + self.Bind(wx.EVT_CHOICE, self.OnChoice, id=self.kernel.GetId()) + self.Bind(wx.EVT_DIRPICKER_CHANGED, self.OnDirChanged, id=self._dbb.GetId()) + def SetYAML(self,yaml:str)->None: """ Set the yaml file """ self.yaml = yaml - def onChecked(self, event): + def OnDirChanged(self, event): + # Get the selected directory + selected_dir = event.GetPath() + StandaloneGUI.last_opened_directory = selected_dir + + def OnChoice(self, event): + selected_option = self.kernel.GetStringSelection() + self._cb4.Enable(selected_option == 'PyPDEVS') + + def OnChecked(self, event): """Add Simulation Kernel Checkbox has been clicked. Args: @@ -212,6 +252,7 @@ def OnOk(self, event): ### call validator for zip name textctrl if self._tc.GetValidator().Validate(self._tc): + format = self.format.GetString(self.format.GetSelection()) zip_name = self._tc.GetLineText(0) zip_dir = self._dbb.GetValue() sim_cb = self._cb1.GetValue() @@ -221,7 +262,7 @@ def OnOk(self, event): kernel = self.kernel.GetString(self.kernel.GetSelection()) ### call the StandaloneNoGui class to build the package depending on the settings from the frame. - standalone = StandaloneNoGUI(self.yaml,zip_name,outdir=zip_dir,add_sim_kernel=sim_cb,add_dockerfile=docker_cb,sim_time=ntl_cb,rt=rt, kernel=kernel) + standalone = StandaloneNoGUI(self.yaml,zip_name,format=format,outdir=zip_dir,add_sim_kernel=sim_cb,add_dockerfile=docker_cb,sim_time=ntl_cb,rt=rt, kernel=kernel) ### Try to build de zip package of the standalone version of DEVSimPy-nogui if standalone.BuildZipPackage(): diff --git a/StandaloneNoGUI.py b/StandaloneNoGUI.py index 05f9a053..0405515a 100644 --- a/StandaloneNoGUI.py +++ b/StandaloneNoGUI.py @@ -20,7 +20,8 @@ # ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## -import os +import os, sys +import subprocess import zipfile import zipfile import configparser @@ -73,7 +74,22 @@ def get_domain_path()->str: return os.path.abspath(built_in['DOMAIN_PATH']) else: return "Domain/" - + +def execute_script(yaml): + # Execute the script using subprocess.Popen + process = subprocess.Popen(["python", "devsimpy-nogui.py", f"{os.path.abspath(yaml)}","10"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + _, stderr = process.communicate() + + # Check if the execution was successful + if process.returncode == 0: + # Get the used packages from sys.modules + used_packages = {mod.split('.')[0] for mod in sys.modules.keys() if '.' in mod} + return True, used_packages + else: + # Print error message and return empty set + print(f"Error executing script: {stderr.decode()}") + return False, set() + class StandaloneNoGUI: ### list of files to zip @@ -84,12 +100,13 @@ class StandaloneNoGUI: ## list of dir to zip DIRNAMES = ["DomainInterface/","Mixins/","Patterns/"] - def __init__(self, yaml:str="", outfn:str="devsimpy-nogui-pkg.zip", outdir:str=os.getcwd(), add_sim_kernel:bool=True, add_dockerfile:bool=False, sim_time:str="ntl", rt:bool=False, kernel:str='PyDEVS'): + def __init__(self, yaml:str="", outfn:str="devsimpy-nogui-pkg.zip", format:str="Minimal", outdir:str=os.getcwd(), add_sim_kernel:bool=True, add_dockerfile:bool=False, sim_time:str="ntl", rt:bool=False, kernel:str='PyDEVS'): """ Generates the zip file with all files needed to execute the devsimpy-nogui script. Args: yaml (str): yaml file to zip (optional) outfn (str): zip file to export all files + format (str): Minimal export only necessary dependancies while Full export all dependancies (less optimal but more secure) outdir (str): directory where zip file is generated add_sim_kernel (bool): zip the simlation kernel add_dockerfile (bool): zip the DockerFile file @@ -101,6 +118,7 @@ def __init__(self, yaml:str="", outfn:str="devsimpy-nogui-pkg.zip", outdir:str=o ### local copy self.yaml = yaml self.outfn = outfn + self.format = format self.outdir = outdir self.add_sim_kernel = add_sim_kernel self.add_dockerfile = add_dockerfile @@ -260,7 +278,25 @@ def BuildZipPackage(self) -> None: ### write config file archive.writestr("config.json", self.GetConfigSpec()) - + + + ################################################################### + ### + ### Requierements files + ### + ################################################################### + + # Example: Execute a script and get the used packages + success, packages = execute_script(self.yaml) + + if success: + print("Used packages:") + for package in packages: + print(package) + else: + print("Script execution failed.") + + ### add requirements-nogui.txt file ### TODO: packages used in models include in the yaml file are not defined in the requirements.txt! archive.write('requirements-nogui.txt', 'requirements.txt') From 92516da719dc3ce3065ac67a5b8faac35f1139a7 Mon Sep 17 00:00:00 2001 From: Capocchi Date: Tue, 12 Dec 2023 10:58:16 +0100 Subject: [PATCH 2/2] update --- StandaloneGUI.py | 18 ++++++++------- StandaloneNoGUI.py | 56 ++++++++++++++++++++++++---------------------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/StandaloneGUI.py b/StandaloneGUI.py index 359056bb..03b8f351 100644 --- a/StandaloneGUI.py +++ b/StandaloneGUI.py @@ -118,7 +118,7 @@ def InitUI(self): self.SetSize((-1, 250)) panel = wx.Panel(self) - + vbox = wx.BoxSizer(wx.VERTICAL) ### Zip name field @@ -187,10 +187,11 @@ def InitUI(self): self.kernel = wx.Choice(panel, -1, choices=["PyDEVS", "PyPDEVS"]) self.kernel.SetSelection(0) self.kernel.Enable(False) - label = wx.StaticText(panel, -1, _("Kernel:")) - + self.kernel_label = wx.StaticText(panel, -1, _("Kernel:")) + self.kernel_label.Enable(False) + box = wx.BoxSizer(wx.HORIZONTAL) - box.Add(label, 0, wx.ALIGN_CENTER_VERTICAL) + box.Add(self.kernel_label, 0, wx.ALIGN_CENTER_VERTICAL) box.Add(self.kernel, 0, wx.ALIGN_CENTER_VERTICAL, border=5) hbox5.Add(box) @@ -202,8 +203,8 @@ def InitUI(self): ### Buttons hbox5 = wx.BoxSizer(wx.HORIZONTAL) btn1 = wx.Button(panel, wx.ID_OK, _("Ok"), size=(70, 30)) - btn2 = wx.Button(panel, wx.ID_CLOSE, _("Close"), size=(70, 30)) - hbox5.Add(btn2) + # btn2 = wx.Button(panel, wx.ID_CLOSE, _("Close"), size=(70, 30)) + # hbox5.Add(btn2) hbox5.Add(btn1, flag=wx.LEFT|wx.BOTTOM) vbox.Add(hbox5, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=10) @@ -211,7 +212,7 @@ def InitUI(self): ### Binds self.Bind(wx.EVT_BUTTON, self.OnOk, id=btn1.GetId()) - self.Bind(wx.EVT_BUTTON, self.OnClose, id=btn2.GetId()) + # self.Bind(wx.EVT_BUTTON, self.OnClose, id=btn2.GetId()) self.Bind(wx.EVT_CHECKBOX,self.OnChecked, id=self._cb1.GetId()) self.Bind(wx.EVT_CHOICE, self.OnChoice, id=self.kernel.GetId()) self.Bind(wx.EVT_DIRPICKER_CHANGED, self.OnDirChanged, id=self._dbb.GetId()) @@ -230,7 +231,7 @@ def OnDirChanged(self, event): def OnChoice(self, event): selected_option = self.kernel.GetStringSelection() self._cb4.Enable(selected_option == 'PyPDEVS') - + def OnChecked(self, event): """Add Simulation Kernel Checkbox has been clicked. @@ -241,6 +242,7 @@ def OnChecked(self, event): # cb.GetLabel(),' is clicked',cb.GetValue() self.kernel.Enable(cb.IsChecked()) + self.kernel_label.Enable(cb.IsChecked()) @BuzyCursorNotification def OnOk(self, event): diff --git a/StandaloneNoGUI.py b/StandaloneNoGUI.py index 0405515a..d6bb034a 100644 --- a/StandaloneNoGUI.py +++ b/StandaloneNoGUI.py @@ -146,7 +146,7 @@ def GetDockerSpec(self): """ """ return f""" - FROM python:3.8-slim-buster + FROM python:3.10-slim-buster WORKDIR /app @@ -210,32 +210,32 @@ def BuildZipPackage(self) -> None: ### ################################################################### - ### add the Domain libairies according to the DOAMIN_PATH var - yaml = YAMLHandler(path) - - ### to not insert two times the same file - added_files = set() - ### lib_path is the directory of the library involved in the yaml model - for path in yaml.extractPythonPaths(): - lib_path = os.path.dirname(path) - if lib_path.endswith(('.amd','.cmd')): - lib_path = os.path.dirname(lib_path) - - ### format the path of the library to include in the archive - lib_name = os.path.basename(os.path.dirname(lib_path)) - for file in retrieve_file_paths(lib_path): - if file.endswith(('.py', '.amd', '.cmd')) and '__pycache__' not in file: - relative_path = 'Domain'+file.split(lib_name)[1] - if relative_path not in added_files: - archive.write(file, arcname=relative_path) - added_files.add(relative_path) - - ### To include all Domain dir - # for file in retrieve_file_paths(self.domain_path): - # if file.endswith(('.py', '.amd', '.cmd')) and \ - # '__pycache__' not in file and \ - # os.path.basename(os.path.dirname(file)) in lib_to_inculde: - # archive.write(file, arcname='Domain'+os.path.join(file.split('Domain')[1], os.path.basename(file))) + if self.format == "Minimal": + ### add the Domain libairies according to the DOAMIN_PATH var + yaml = YAMLHandler(path) + + ### to not insert two times the same file + added_files = set() + ### lib_path is the directory of the library involved in the yaml model + for path in yaml.extractPythonPaths(): + lib_path = os.path.dirname(path) + if lib_path.endswith(('.amd','.cmd')): + lib_path = os.path.dirname(lib_path) + + ### format the path of the library to include in the archive + lib_name = os.path.basename(os.path.dirname(lib_path)) + for file in retrieve_file_paths(lib_path): + if file.endswith(('.py', '.amd', '.cmd')) and '__pycache__' not in file: + relative_path = 'Domain'+file.split(lib_name)[1] + if relative_path not in added_files: + archive.write(file, arcname=relative_path) + added_files.add(relative_path) + else: + ## To include all Domain dir + for file in retrieve_file_paths(self.domain_path): + if file.endswith(('.py', '.amd', '.cmd')) and \ + '__pycache__' not in file: + archive.write(file, arcname='Domain'+os.path.join(file.split('Domain')[1], os.path.basename(file))) ################################################################### ### @@ -286,6 +286,8 @@ def BuildZipPackage(self) -> None: ### ################################################################### + ### TODO: include the self.format condition to choose if you want a minimal or full dependencies + # Example: Execute a script and get the used packages success, packages = execute_script(self.yaml)