PaIRS-UniNa 0.2.5__cp311-cp311-win_amd64.whl → 0.2.6__cp311-cp311-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of PaIRS-UniNa might be problematic. Click here for more details.

PaIRS_UniNa/Changes.txt CHANGED
@@ -1,4 +1,16 @@
1
- ********* Changes in version 0.2.5 (2025.07.18) **********
1
+ ********* Changes in version 0.2.6 (2025.09.06) **********
2
+ Bug fixes:
3
+ - fixed bugs related to process tree management.
4
+
5
+ User-interface enhancements:
6
+ - enhanced release check using SSL for reliable retrieval of the latest version.
7
+
8
+ Distribution:
9
+ - ready-to-use executables of PaIRS are now available!
10
+
11
+
12
+
13
+ ********* Changes in version 0.2.5 (2025.07.28) **********
2
14
  Bug fixes:
3
15
  - fixed bugs in launching PIV and stereoscopic PIV processes in special cases;
4
16
  - fixed tooltip crash introduced by PySide 6.9;
@@ -17,7 +29,7 @@ User-interface enhancements:
17
29
 
18
30
  Distribution:
19
31
  - Support for Python 3.9 has been discontinued;
20
- - Python 3.13 builds have been successfully created and tested.
32
+ - Python 3.13 builds have been successfully created and tested in Windows.
21
33
 
22
34
 
23
35
 
PaIRS_UniNa/Explorer.py CHANGED
@@ -1318,7 +1318,7 @@ class ProcessTree(PaIRSTree):
1318
1318
  par.ind[1]=int(self.FlagBin)
1319
1319
  par.ind[2]=i
1320
1320
  except Exception as inst:
1321
- pri.Error.red(inst)
1321
+ pri.Error.red(f"{inst}\n{traceback.format_exc()}")
1322
1322
  pass
1323
1323
  if hasattr(self.gui,'setLinks'):
1324
1324
  inds_new_list=list(inds_new)
@@ -1383,7 +1383,7 @@ class ProcessTree(PaIRSTree):
1383
1383
  if self.FlagExternalDrag:
1384
1384
  self.FlagExternalDrag=False
1385
1385
  self.dragged_items=None
1386
- self.setStyleSheet("")
1386
+ self.setStyleSheet(self.initialStyleSheet)
1387
1387
  if self.parent(): self.setPalette(self.parent().palette())
1388
1388
  self.hovered_item=None
1389
1389
 
@@ -1424,7 +1424,7 @@ class ProcessTree(PaIRSTree):
1424
1424
  if not self.widgets: return
1425
1425
  for w in self.widgets:
1426
1426
  w:gPaIRS_Tab
1427
- w.gen_TABpar(ITE.ind,FlagEmptyPrev=True,FlagInsert=3,Process=ITE.Process,Step=ITE.Step)
1427
+ w.gen_TABpar(ITE.ind,FlagEmptyPrev=True,FlagInsert=2,Process=ITE.Process,Step=ITE.Step)
1428
1428
  pass
1429
1429
  return
1430
1430
 
@@ -1434,11 +1434,11 @@ class ProcessTree(PaIRSTree):
1434
1434
  if t in self.widgetNames:
1435
1435
  k=self.widgetNames.index(t)
1436
1436
  w:gPaIRS_Tab=self.widgets[k]
1437
- w.gen_TABpar(ITE.ind,FlagInsert=4,Process=ITE.Process,Step=ITE.Step)
1437
+ w.gen_TABpar(ITE.ind,FlagInsert=3,Process=ITE.Process,Step=ITE.Step)
1438
1438
  pass
1439
1439
  for w, wn in zip(self.widgets,self.widgetNames):
1440
1440
  if wn not in ITE.tabs+['TabArea']:
1441
- w.gen_TABpar(ITE.ind,FlagNone=True,FlagInsert=4,Process=ITE.Process,Step=ITE.Step)
1441
+ w.gen_TABpar(ITE.ind,FlagNone=True,FlagInsert=3,Process=ITE.Process,Step=ITE.Step)
1442
1442
  pass
1443
1443
  return
1444
1444
 
@@ -1697,10 +1697,14 @@ class ProcessTree(PaIRSTree):
1697
1697
  if errorString:
1698
1698
  if '_data' in filename:
1699
1699
  try:
1700
+ errorString=''
1700
1701
  basename,ext=os.path.splitext(filename)
1701
1702
  filename2=basename[:-5]+ext
1702
1703
  data, errorMessage=loadList(filename2)
1703
- errorString=errorString+errorMessage if errorMessage else ''
1704
+ #errorString=errorString+errorMessage if errorMessage else ''
1705
+ if errorMessage:
1706
+ WarningMessage="It was not possible to determine which process the selected data file belongs to.\nPlease, try again by loading the process output file directly."
1707
+ warningDialog(self,WarningMessage)
1704
1708
  except Exception as inst2:
1705
1709
  errorString+=str(inst2)
1706
1710
  FlagError=True
PaIRS_UniNa/Input_Tab.py CHANGED
@@ -197,6 +197,7 @@ class Input_Tab(gPaIRS_Tab):
197
197
 
198
198
  self.ImTreeInd=[]
199
199
  self.ui.imTreeWidget.imTree.signals.stopWorker.connect(self.emptyImTreeInd)
200
+ self.ui.imTreeWidget.FlagInGui=True
200
201
 
201
202
  self.FlagScanPath=False
202
203
  pri.Time.yellow('Input: define callbacks')
@@ -312,10 +313,10 @@ class Input_Tab(gPaIRS_Tab):
312
313
 
313
314
  self.ui.spin_inp_ncam.setEnabled(self.INPpar.FlagCam)
314
315
  self.ui.spin_inp_ncam.setMinimum(self.ncamMinimum())
315
- self.ui.imTreeWidget.spin_ncam.setEnabled(self.INPpar.FlagCam)
316
+ self.ui.imTreeWidget.spin_ncam.setEnabled(False)
316
317
  self.ui.imTreeWidget.FlagCam=self.INPpar.FlagCam
317
318
  self.ui.imTreeWidget.spin_ncam.setMinimum(self.ncamMinimum())
318
- self.ui.imTreeWidget.spin_ncam.setValue(self.INPpar.ncam)
319
+ self.ui.imTreeWidget.spin_ncam.setValue(self.INPpar.importPar.inp_ncam)
319
320
  self.ui.spin_inp_cam.setEnabled(self.INPpar.inp_ncam>1)
320
321
  self.ui.spin_inp_cam.setMaximum(self.INPpar.inp_ncam)
321
322
  self.setImageNumberSpinLimits()
@@ -418,6 +419,8 @@ class Input_Tab(gPaIRS_Tab):
418
419
  self.ui.line_edit_path.setText(currpath)
419
420
  self.INPpar.path=self.ui.line_edit_path.text()
420
421
  self.line_edit_path_preaction()
422
+ else:
423
+ currpath='./'
421
424
 
422
425
  def button_path_action_future(self):
423
426
  self.line_edit_path_action_future()
@@ -311,7 +311,7 @@ class Input_Tab_CalVi(gPaIRS_Tab):
311
311
 
312
312
  def line_edit_cameras_action(self):
313
313
  text=self.ui.line_edit_cameras.text()
314
- split_text=re.findall('(\d+)', text)
314
+ split_text=re.findall(r'(\d+)', text)
315
315
  self.INPpar.cams=[]
316
316
  for s in split_text:
317
317
  i=int(s)
@@ -365,7 +365,7 @@ class Input_Tab_CalVi(gPaIRS_Tab):
365
365
  f=os.path.basename(filename)
366
366
  FlagWarn=False
367
367
  if self.INPpar.FlagCam:
368
- fsplitted=re.split('_cam\d+', f)
368
+ fsplitted=re.split(r'_cam\d+', f)
369
369
  if len(fsplitted)>1:
370
370
  fsplitted.insert(-1,'_cam*')
371
371
  f="".join(fsplitted)
@@ -508,7 +508,7 @@ class Input_Tab_CalVi(gPaIRS_Tab):
508
508
  if self.INPpar.FlagCam and len(self.INPpar.cams)==0:
509
509
  ncam=0
510
510
  for f in self.INPpar.filenames:
511
- pats=re.findall('_cam\d+', f)
511
+ pats=re.findall(r'_cam\d+', f)
512
512
  if len(pats):
513
513
  ncam=int(pats[-1].replace("_cam",""))
514
514
  break
@@ -534,7 +534,7 @@ class Input_Tab_CalVi(gPaIRS_Tab):
534
534
  if self.INPpar.FlagCam:
535
535
  for k,f in enumerate(self.INPpar.filenames):
536
536
  if '_cam*' in f: continue
537
- fsplitted=re.split('_cam\d+', f)
537
+ fsplitted=re.split(r'_cam\d+', f)
538
538
  fsplitted.insert(-1,'_cam*')
539
539
  f="".join(fsplitted)
540
540
  self.INPpar.filenames[k]=f
@@ -104,7 +104,7 @@ class ImageSet(TABpar):
104
104
 
105
105
  def addPattern(self,basename):
106
106
  _, ext = os.path.splitext(basename)
107
- split_basename=re.split('(\d+)', basename)
107
+ split_basename=re.split(r'(\d+)', basename)
108
108
  c=0
109
109
  for k,s in enumerate(split_basename):
110
110
  if not len(s): continue
@@ -172,7 +172,7 @@ class ImageSet(TABpar):
172
172
  while i<len(jl) and p2[diff[0]]<l[i]: i+=1
173
173
  jl.insert(i-1,j)
174
174
  l.insert(i-1,p2[diff[0]])
175
- self.link[k]=jalpha+jnumber+[k]
175
+ self.link[k]=jalpha+sorted(jnumber)+[k]
176
176
  return
177
177
 
178
178
  def print(self):
@@ -1604,6 +1604,7 @@ class ImageTreeWidget(QWidget):
1604
1604
  self.signals=self.ImageTreeWidget_signals()
1605
1605
  self.FlagSpinButtons=FlagSpinButtons
1606
1606
  self.FlagCam=True
1607
+ self.FlagInGui=False
1607
1608
 
1608
1609
  font=self.font()
1609
1610
  font.setItalic(True)
@@ -2208,7 +2209,7 @@ class ImageTreeWidget(QWidget):
2208
2209
  self.spin_cam.setStatusTip(self.spin_cam.toolTip())
2209
2210
  FlagSingleTree=type(self.imTree)==SingleImageTree
2210
2211
  self.spin_cam.setEnabled(self.imTree.ncam>1 and (self.imTree.nimg>1 or FlagSingleTree))
2211
- self.spin_ncam.setEnabled(not FlagSingleTree and self.FlagCam)
2212
+ self.spin_ncam.setEnabled(not FlagSingleTree and self.FlagCam and not self.FlagInGui)
2212
2213
 
2213
2214
  self.spin_frame.setToolTip(f'Current frame. Number of frames: {self.spin_frame.maximum()}')
2214
2215
  self.spin_frame.setStatusTip(self.spin_frame.toolTip())
@@ -15,10 +15,12 @@ basefold_DEBUG='./'
15
15
  basefold_DEBUG_VIS=''
16
16
  #basefold='B:/dl/apairs/jetcross'
17
17
 
18
+
18
19
  developerIDs={
19
20
  'GP_Win_Office': '231128824800632', #'0x7824af430781',
20
21
  'GP_Win_Office_New': '140626882900161', #'0x7824af430781',
21
22
  'GP_Mac_Laptop': 'V94LRP93FV', #'0xa275dd445ab0',
23
+ 'GP_WSL' : 'b44ec1c0e5a74ffd97bb050c39ef6cb1',
22
24
  'TA_Win_Office': '160983906000941', #'0xccb0da8c896e'
23
25
  'TA_Win_Office_New': '231128824801036', #??
24
26
  }
@@ -29,9 +31,32 @@ def getCurrentID():
29
31
  serial_number=None
30
32
  try:
31
33
  if psutil.LINUX:
34
+ def get_linux_serial():
35
+ from pathlib import Path
36
+ candidates = [
37
+ Path("/sys/class/dmi/id/board_serial"),
38
+ Path("/sys/class/dmi/id/product_uuid"),
39
+ Path("/etc/machine-id"), Path("/var/lib/dbus/machine-id") #WSL
40
+ ]
41
+ for p in candidates:
42
+ try:
43
+ if p.is_file():
44
+ val = p.read_text(errors="ignore").strip()
45
+ if val and val.lower() not in {
46
+ "none", "unknown", "not specified", "to be filled by o.e.m."
47
+ }:
48
+ return val
49
+ except Exception as e:
50
+ if Flag_DEBUG:
51
+ print(f"Error while retrieving motherboard serial number: {e}")
52
+ continue
53
+ return None
54
+ serial_number = get_linux_serial()
55
+ """"
32
56
  # On Linux, the motherboard serial number can be obtained from the /sys/class/dmi/id/board_serial file
33
57
  with open('/sys/class/dmi/id/board_serial', 'r') as f:
34
58
  serial_number = f.read().strip()
59
+ """
35
60
  elif psutil.WINDOWS:
36
61
  # On Windows, the motherboard serial number can be obtained using WMI
37
62
  output = subprocess.check_output(["wmic", "baseboard", "get", "SerialNumber"]).decode('utf-8')
@@ -43,7 +68,8 @@ def getCurrentID():
43
68
  if b'Serial Number (system)' in line:
44
69
  serial_number = line.split(b':')[1].strip().decode('utf-8')
45
70
  except Exception as e:
46
- print(f"Error while retrieving motherboard serial number: {e}")
71
+ if Flag_DEBUG:
72
+ print(f"Error while retrieving motherboard serial number: {e}")
47
73
  return serial_number
48
74
 
49
75
  currentID=getCurrentID()
@@ -58,6 +84,16 @@ if currentID in (developerIDs['GP_Win_Office'],developerIDs['GP_Win_Office_New']
58
84
  'C:/desk/PIV_Img/_data/Calibration_data/cylinder/',
59
85
  ]
60
86
  basefold_DEBUG_VIS='C:/desk/PIV_Img/_data/PIV_data/real_case/'
87
+ elif currentID==developerIDs['GP_WSL']:
88
+ basefold_DEBUG='/mnt/c/desk/PIV_Img/_data/PIV_data/virtual_case/'
89
+ basefold_DEBUGOptions=[
90
+ '/mnt/c/desk/PIV_Img/img1/',
91
+ '/mnt/c/desk/PIV_Img/_data/PIV_data/virtual_case/',
92
+ '/mnt/c/desk/PIV_Img/_data/PIV_data/real_case/',
93
+ '/mnt/c/desk/PIV_Img/_data/Calibration_data/pinhole/',
94
+ '/mnt/c/desk/PIV_Img/_data/Calibration_data/cylinder/',
95
+ ]
96
+ basefold_DEBUG_VIS='/mnt/c/desk/PIV_Img/_data/PIV_data/real_case/'
61
97
  elif currentID==developerIDs['GP_Mac_Laptop']: #gerardo mac
62
98
  basefold_DEBUG='/Users/gerardo/Desktop/PIV_Img/swirler_png/' #'/Users/gerardo/Desktop/PIV_Img/img1/'
63
99
  basefold_DEBUGOptions=[
@@ -72,12 +108,12 @@ elif currentID==developerIDs['GP_Mac_Laptop']: #gerardo mac
72
108
  basefold_DEBUG_VIS='/Users/gerardo/Desktop/PaIRS_examples/PIV_data/real_case/'
73
109
  basefold_DEBUG_VIS='/Users/gerardo/Desktop/PIV_Img/img1/'
74
110
  elif currentID in (developerIDs['TA_Win_Office'],developerIDs['TA_Win_Office_New']): #TA windows
75
- basefold_DEBUG='C:\desk\Attuali\PythonLibC\PIV\img'
111
+ basefold_DEBUG=r'C:\desk\Attuali\PythonLibC\PIV\img'
76
112
  basefold_DEBUGOptions=[
77
113
  'C:/desk/PIV_Img/img1/',
78
114
  'C:/desk/PIV_Img/swirler_png/',
79
115
  '../../img/calib/',
80
- 'C:\desk\Attuali\PythonLibC\PIV\img',
116
+ r'C:\desk\Attuali\PythonLibC\PIV\img',
81
117
  ]
82
118
  basefold_DEBUG_VIS=''
83
119
  else:
@@ -149,6 +185,37 @@ import sys
149
185
  import concurrent.futures
150
186
  import asyncio
151
187
 
188
+ _old_init_QAction = QAction.__init__
189
+ def _new_init_QAction(self, *args, **kwargs):
190
+ _old_init_QAction(self, *args, **kwargs)
191
+ try:
192
+ self.setIconVisibleInMenu(True)
193
+ except Exception:
194
+ pass
195
+ QAction.__init__ = _new_init_QAction
196
+ _old_init_QMenu = QMenu.__init__
197
+ def _new_init_QMenu(self, *args, **kwargs):
198
+ _old_init_QMenu(self, *args, **kwargs)
199
+ try:
200
+ self.menuAction().setIconVisibleInMenu(True)
201
+ except Exception:
202
+ pass
203
+ QMenu.__init__ = _new_init_QMenu
204
+
205
+ # --- Patch dei metodi QMenu che CREANO/AGGIUNGONO azioni (copre gli overload C++) ---
206
+ _old_addAction = QMenu.addAction
207
+ def _new_addAction(self, *args, **kwargs):
208
+ act:QAction = _old_addAction(self, *args, **kwargs) # può essere creato lato C++
209
+ try:
210
+ if isinstance(act, QAction):
211
+ act.setIconVisibleInMenu(True)
212
+ except Exception:
213
+ pass
214
+ return act
215
+ QMenu.addAction = _new_addAction
216
+
217
+ Flag_ISEXE=getattr(sys, 'frozen', False) #made by pyInstaller
218
+ EXEurl='https://www.pairs.unina.it/#download'
152
219
 
153
220
  class ColorPrint:
154
221
  def __init__(self,flagTime=False,prio=PrintTAPriority.medium,faceStd=PrintTA.faceStd,flagFullDebug=False):
@@ -184,6 +251,7 @@ class GPaIRSPrint:
184
251
  self.Info=ColorPrint(prio=PrintTAPriority.medium)
185
252
  self.Time=ColorPrint(prio=PrintTAPriority.medium if FlagPrintTime else PrintTAPriority.veryLow,flagTime=True,faceStd=PrintTA.faceUnderline)
186
253
  self.Error=ColorPrint(prio=PrintTAPriority.medium,faceStd=PrintTA.faceBold)
254
+ self.IOError=ColorPrint(prio=PrintTAPriority.veryLow,faceStd=PrintTA.faceBold)
187
255
  self.Process=ColorPrint(prio=PrintTAPriority.veryLow)
188
256
  self.Callback=ColorPrint(prio=PrintTAPriority.veryLow)
189
257
  self.TABparDiff=ColorPrint(prio=PrintTAPriority.veryLow)
@@ -237,7 +305,6 @@ if __package__ or "." in __name__:
237
305
  foldPaIRS = foldPaIRS.replace('\\', '/')
238
306
  else:
239
307
  foldPaIRS='./'
240
-
241
308
  class ProcessTypes:
242
309
  null=None
243
310
  min=0
@@ -247,7 +314,7 @@ class ProcessTypes:
247
314
  cal=10
248
315
 
249
316
  singleCamera=[piv]
250
- threeCameras=[tpiv]
317
+ threeCameras=[min,tpiv]
251
318
 
252
319
  class StepTypes:
253
320
  null=None
@@ -291,15 +358,24 @@ class outExt:
291
358
  cfg_calvi='.calvi_cfg'
292
359
  pla='.pairs_pla'
293
360
 
294
-
295
361
 
296
362
  lastcfgname='lastWorkSpace'+outExt.wksp
297
-
298
363
  fileChanges=foldPaIRS+'Changes.txt'
299
- fileWhatsNew=[foldPaIRS+f for f in fileWhatsNew]
300
364
  icons_path=foldPaIRS+icons_path
301
- lastcfgname=foldPaIRS+lastcfgname
302
- pro_path=foldPaIRS+"pro/"
365
+
366
+ if not Flag_ISEXE:
367
+ fileWhatsNew=[foldPaIRS+f for f in fileWhatsNew]
368
+
369
+ lastcfgname=foldPaIRS+lastcfgname
370
+ pro_path=foldPaIRS+"pro/"
371
+ else:
372
+ from pathlib import Path
373
+ exe_dir = str(Path(sys.argv[0]).resolve().parent)+'/'
374
+ fileWhatsNew=[foldPaIRS+fileWhatsNew[0],exe_dir+fileWhatsNew[1]]
375
+ lastcfgname = exe_dir + lastcfgname
376
+ pro_path = exe_dir + "pro/"
377
+
378
+
303
379
  if not os.path.exists(pro_path):
304
380
  try:
305
381
  os.mkdir(pro_path)
@@ -782,12 +858,16 @@ def runPaIRS(self,command='',flagQuestion=True):
782
858
  def run(self):
783
859
  try:
784
860
  import subprocess
785
- if Flag: #launched from package
786
- pri.Info.white(sys.executable+' -m PaIRS_UniNa '+command)
787
- subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
861
+ if Flag_ISEXE:
862
+ pri.Info.white(sys.executable+' '+command)
863
+ subprocess.call(sys.executable+' '+command,shell=True)
788
864
  else:
789
- pri.Info.white(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"')
790
- subprocess.call(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"',shell=True)
865
+ if Flag: #launched from package
866
+ pri.Info.white(sys.executable+' -m PaIRS_UniNa '+command)
867
+ subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
868
+ else:
869
+ pri.Info.white(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"')
870
+ subprocess.call(sys.executable+' -c '+'"'+f"import os; os.chdir('{os.getcwd()}'); {pyCommands[command]}"+'"',shell=True)
791
871
  self.isRunning=False
792
872
  except Exception as inst:
793
873
  pri.Error.red(inst)
@@ -816,12 +896,12 @@ def showSplash(filename=''+ icons_path +'logo_PaIRS_completo.png'):
816
896
  splash.show()
817
897
  return splash
818
898
 
819
- def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None):
899
+ def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None,flagWarning=1):
820
900
  flagStopAndDownload=False
821
901
  var=self.TABpar
822
902
  #var.FlagOutDated=0 if currentVersion==var.latestVersion else var.FlagOutDated
823
903
  if abs(var.FlagOutDated)==1:
824
- warningLatestVersion(self,app,flagExit=0,flagWarning=1,FlagStayOnTop=True)
904
+ warningLatestVersion(self,app,flagExit=0,flagWarning=flagWarning,FlagStayOnTop=True)
825
905
  var.FlagOutDated=2 if var.FlagOutDated==1 else -2
826
906
  """
827
907
  flagStopAndDownload=questionDialog(self,f'A new version of the PaIRS_UniNa package is available. Do you want to download it before starting the current istance of {self.name}?')
@@ -852,7 +932,7 @@ def checkLatestVersion(self,version,app:QApplication=None,splash:QLabel=None):
852
932
  var.FlagOutDated=-2 if var.FlagOutDated==-2 else -1
853
933
  elif flagOutDated==-1000:
854
934
  sOut=f'Error from pip: it was not possible to check for a new version of the {packageName} package!'
855
- var.FlagOutDated=0
935
+ var.FlagOutDated=-1000
856
936
  else:
857
937
  sOut=f'{packageName} The current version ({currentVersion}) of {packageName} is up-to-date! Enjoy it!'
858
938
  var.FlagOutDated=0
@@ -872,13 +952,18 @@ def warningLatestVersion(self,app,flagExit=0,flagWarning=0,time_milliseconds=0,F
872
952
  py=myStandardRoot(sys.executable).split('/')[-1].split('.')[0]
873
953
  command=f'{py} -m pip install --upgrade PaIRS_UniNa'
874
954
  if self.TABpar.FlagOutDated>0:
875
- Message=f'A new version of the PaIRS_UniNa package is available (current: {self.TABpar.currentVersion}, latest: {self.TABpar.latestVersion}).\nPlease, {exitSuggestion}install it with the following command:\n{command}'
955
+ if Flag_ISEXE:
956
+ Message=f'A new version of the PaIRS_UniNa package is available (current: {self.TABpar.currentVersion}, latest: {self.TABpar.latestVersion}).\nPlease, download it from the following link:\n{EXEurl}'
957
+ else:
958
+ Message=f'A new version of the PaIRS_UniNa package is available (current: {self.TABpar.currentVersion}, latest: {self.TABpar.latestVersion}).\nPlease, {exitSuggestion}install it with the following command:\n{command}'
959
+ elif self.TABpar.FlagOutDated==-1000:
960
+ Message = ("Unable to check for the latest official release of PaIRS_UniNa. Please check the PyPI page manually for updates:\n""https://pypi.org/project/PaIRS-UniNa/")
876
961
  else:
877
962
  Message=f'The version of the current instance of PaIRS_UniNa ({self.TABpar.currentVersion}) is newer than the latest official releas ({self.TABpar.latestVersion})!\nYou should contact Tommaso and Gerardo if you are a developer and some relevant change is made by yourself!\nIf you are a user, enjoy this beta version and please report any issue!'
878
963
  if flagExit:
879
964
  print(f"\n{'*'*100}\n"+Message+f"\n{'*'*100}\n")
880
965
  if flagWarning:
881
- warningDialog(self,Message,time_milliseconds=time_milliseconds,flagScreenCenter=True,pixmap=''+ icons_path +'flaticon_PaIRS_download.png' if self.TABpar.FlagOutDated>0 else ''+ icons_path +'flaticon_PaIRS_beta.png',FlagStayOnTop=FlagStayOnTop,addButton={"See what's new!": lambda: QDesktopServices.openUrl(QUrl("https://pypi.org/project/PaIRS-UniNa/"))} if self.TABpar.FlagOutDated>0 else {})
966
+ warningDialog(self,Message,time_milliseconds=time_milliseconds,flagScreenCenter=True,pixmap=''+ icons_path +'flaticon_PaIRS_download.png' if self.TABpar.FlagOutDated>0 else ''+ icons_path +'flaticon_PaIRS_download_warning.png' if self.TABpar.FlagOutDated==-1000 else ''+ icons_path +'flaticon_PaIRS_beta.png',FlagStayOnTop=FlagStayOnTop,addButton={"Go to the download page!": lambda: QDesktopServices.openUrl(QUrl(EXEurl))} if Flag_ISEXE else {"See what's new!": lambda: QDesktopServices.openUrl(QUrl("https://pypi.org/project/PaIRS-UniNa/"))} if self.TABpar.FlagOutDated>0 else {})
882
967
 
883
968
  def downloadLatestVersion(self,app):
884
969
  try:
@@ -902,6 +987,7 @@ def downloadLatestVersion(self,app):
902
987
 
903
988
  def button_download_PaIRS_action(self,app):
904
989
  warningLatestVersion(self,app,flagExit=0,flagWarning=1)
990
+ checkLatestVersion(self,__version__,self.app,splash=None,flagWarning=0)
905
991
  return
906
992
  flagStopAndDownload=questionDialog(self,f'A new version of the PaIRS_UniNa package is available. Do you want to close the current instance of {self.name} and download it?')
907
993
  if not flagStopAndDownload: return
@@ -913,6 +999,20 @@ def button_download_PaIRS_action(self,app):
913
999
  else: command=''
914
1000
  subprocess.call(sys.executable+' -m PaIRS_UniNa '+command,shell=True)
915
1001
  #runPaIRS(self,flagQuestion=False)
1002
+
1003
+ import urllib.request, json, ssl
1004
+
1005
+ def get_package_version_urllib(package_name):
1006
+ """Get package version using only standard library"""
1007
+ try:
1008
+ import certifi
1009
+ url = f"https://pypi.org/pypi/{package_name}/json"
1010
+ context = ssl.create_default_context(cafile=certifi.where())
1011
+ with urllib.request.urlopen(url, context=context, timeout=10) as response:
1012
+ data = json.loads(response.read().decode())
1013
+ return True, data['info']['version']
1014
+ except Exception as e:
1015
+ return False, f"Error: {e}"
916
1016
 
917
1017
  def checkOutDated(packageName:str,printOutDated):
918
1018
  '''
@@ -944,48 +1044,60 @@ def checkOutDated(packageName:str,printOutDated):
944
1044
  currentVersion='none'
945
1045
  latestVersion=''
946
1046
  try:
947
- if Flag_DEBUG:
1047
+ if Flag_ISEXE:
948
1048
  currentVersion=__version__+'.'+__subversion__ if int(__subversion__) else __version__
949
1049
  else:
950
- command=[sys.executable, '-m', 'pip', 'show', packageName]
951
- reqs = subprocess.run(command,capture_output=True)
952
- if reqs.returncode:
953
- pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
954
- return flagOutDated,currentVersion,latestVersion
955
- printing=reqs.stdout.decode("utf-8")
956
- pri.Info.cyan( printing )
957
- r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
958
- currentVersion='none'
959
- for s in r:
960
- if 'Version: ' in s:
961
- currentVersion=s.replace('Version: ','')
962
- break
1050
+ if Flag_DEBUG:
1051
+ currentVersion=__version__+'.'+__subversion__ if int(__subversion__) else __version__
1052
+ else:
1053
+ command=[sys.executable, '-m', 'pip', 'show', packageName]
1054
+ reqs = subprocess.run(command,capture_output=True)
1055
+ if reqs.returncode:
1056
+ pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
1057
+ return flagOutDated,currentVersion,latestVersion
1058
+ printing=reqs.stdout.decode("utf-8")
1059
+ pri.Info.cyan( printing )
1060
+ r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
1061
+ currentVersion='none'
1062
+ for s in r:
1063
+ if 'Version: ' in s:
1064
+ currentVersion=s.replace('Version: ','')
1065
+ break
963
1066
  if currentVersion!=__version__:
964
1067
  message=f'Greetings, developer!\nThe version of the current instance of PaIRS_UniNa ({__version__}) is different from that installed in the present Python environment ({currentVersion})!\nYou should contact Tommaso and Gerardo if some relevant change is made by yourself!'
965
1068
  pri.Info.yellow(f'{"-"*50}\n{message}\n{"-"*50}\n')
966
- command=[sys.executable, '-m', 'pip', 'index','versions',packageName]
967
- reqs = subprocess.run(command,capture_output=True)
968
- if not reqs.returncode:
969
- printing=reqs.stdout.decode("utf-8")
970
- pri.Info.cyan( printing )
971
- r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
972
- #currentVersion=r[0].replace(packageName,'').replace('(','').replace(')','').replace(' ','')
973
- latestVersion=r[1].replace('Available versions: ','').split(',')[0]
1069
+ if Flag_ISEXE:
1070
+ _, latestVersion = get_package_version_urllib("PaIRS_UniNa")
974
1071
  else:
975
- pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
976
-
977
- command=[sys.executable, '-m', 'pip', 'list','--outdated']
1072
+ command=[sys.executable, '-m', 'pip', 'index', 'versions', packageName]
978
1073
  reqs = subprocess.run(command,capture_output=True)
979
- if reqs.returncode:
980
- pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
981
- return flagOutDated,currentVersion,latestVersion
982
- outDated = [r.decode().split('==')[0] for r in reqs.stdout.split()]
983
- if packageName in outDated:
984
- i=outDated.index(packageName)
985
- latestVersion=outDated[i+2]
1074
+ if not reqs.returncode:
1075
+ printing=reqs.stdout.decode("utf-8")
1076
+ pri.Info.cyan( printing )
1077
+ r=reqs.stdout.decode("utf-8").replace('\r','').split('\n')
1078
+ #currentVersion=r[0].replace(packageName,'').replace('(','').replace(')','').replace(' ','')
1079
+ latestVersion=r[1].replace('Available versions: ','').split(',')[0]
986
1080
  else:
987
- latestVersion=currentVersion
988
- pri.Info.cyan(f'{packageName} ({currentVersion}). Latest version available: {latestVersion}')
1081
+ flagOk,latestVersion=get_package_version_urllib(packageName)
1082
+ if not flagOk:
1083
+ pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
1084
+ pri.Error.red(latestVersion)
1085
+ latestVersion='none'
1086
+
1087
+ """
1088
+ command=[sys.executable, '-m', 'pip', 'list','--outdated']
1089
+ reqs = subprocess.run(command,capture_output=True)
1090
+ if reqs.returncode:
1091
+ pri.Error.red('Error in command:\n'+' '.join(command)+'\n'+reqs.stderr.decode("utf-8") )
1092
+ return flagOutDated,currentVersion,latestVersion
1093
+ outDated = [r.decode().split('==')[0] for r in reqs.stdout.split()]
1094
+ if packageName in outDated:
1095
+ i=outDated.index(packageName)
1096
+ latestVersion=outDated[i+2]
1097
+ else:
1098
+ latestVersion=currentVersion
1099
+ pri.Info.cyan(f'{packageName} ({currentVersion}). Latest version available: {latestVersion}')
1100
+ """
989
1101
  #flagOutDated=1 if currentVersion!=latestVersion else 0
990
1102
  cV_parts=[int(c) for c in currentVersion.split('.')]
991
1103
  lV_parts=[int(c) for c in latestVersion.split('.')]
@@ -1165,6 +1277,19 @@ def resetRequiredPackagesFile(filename=rqrdpckgs_filename):
1165
1277
  else:
1166
1278
  pri.Error.red(f"resetRequiredPackagesFile: Skipping malformed line: {line}")
1167
1279
 
1280
+ def to_triplet(v: Version) -> tuple[int,int,int]:
1281
+ r = v.release or (0,)
1282
+ return (r[0], r[1] if len(r) > 1 else 0, r[2] if len(r) > 2 else 0)
1283
+
1284
+ def le_ver(a: Version, b: Version) -> bool:
1285
+ a1,a2,a3 = to_triplet(a)
1286
+ b1,b2,b3 = to_triplet(b)
1287
+ if a1 > b1: return False
1288
+ if a1 < b1: return True
1289
+ if a2 > b2: return False
1290
+ if a2 < b2: return True
1291
+ return a3 <= b3
1292
+
1168
1293
  def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False, FlagForcePrint=False):
1169
1294
  required_packages = []
1170
1295
  vmin_list = []
@@ -1199,7 +1324,7 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1199
1324
  Flag = True
1200
1325
 
1201
1326
  # Check if within [vmin, vmax]
1202
- if not (vmin_list[i] <= installed_version <= vmax_list[i]):
1327
+ if not (le_ver(vmin_list[i],installed_version) and le_ver(installed_version,vmax_list[i])):
1203
1328
  """
1204
1329
  warnings.append(
1205
1330
  f"- {pkg}: installed = {installed_version}, target range = [{vmin_list[i]}, {vmax_list[i]}]"
@@ -1216,7 +1341,8 @@ def checkRequiredPackages(self, filename=rqrdpckgs_filename, FlagDisplay=False,
1216
1341
  "Some installed packages have a version outside the target range used to develop "
1217
1342
  "the current release of the PaIRS_UniNa package.\n\n"
1218
1343
  "This may lead to compatibility issues. If you experience unexpected behavior, "
1219
- "it is recommended to either reinstall the last tested compatible versions."
1344
+ "it is recommended to either reinstall the last tested compatible versions or "
1345
+ f"download the executable at {EXEurl}."
1220
1346
  f" If any issue occurs, please contact the authors at {__mail__}.\n\n"
1221
1347
  #"or use the standalone executable available at:\n"
1222
1348
  #"https://pairs.unina.it/#download\n\n"
@@ -724,7 +724,7 @@ class Process_Tab(gPaIRS_Tab):
724
724
  #******************** Actions
725
725
  def edit_Wind_vectors(self,wedit:QLineEdit,wlab:QLabel):
726
726
  text=wedit.text()
727
- split_text=re.split('(\d+)', text)[1:-1:2]
727
+ split_text=re.split(r'(\d+)', text)[1:-1:2]
728
728
  vect=[int(i) for i in split_text]
729
729
  FlagEmpty=len(vect)==0
730
730
  if FlagEmpty: FlagError=True
@@ -1291,7 +1291,7 @@ class Process_Tab(gPaIRS_Tab):
1291
1291
 
1292
1292
  def line_edit_IW_action(self):
1293
1293
  text=self.ui.line_edit_IW.text()
1294
- split_text=re.split('(\d+)', text)[1:-1:2]
1294
+ split_text=re.split(r'(\d+)', text)[1:-1:2]
1295
1295
  vect=[int(split_text[i]) for i in (0,2,1,3)]
1296
1296
  if len(vect)==4:
1297
1297
  k=self.PROpar.row
@@ -133,7 +133,7 @@ class Process_Tab_Disp(gPaIRS_Tab):
133
133
  #******************** Actions
134
134
  def line_edit_IW_action(self):
135
135
  text=self.ui.line_edit_IW.text()
136
- split_text=re.split('(\d+)', text)[1:-1:2]
136
+ split_text=re.split(r'(\d+)', text)[1:-1:2]
137
137
  split_num=[int(t) for t in split_text]
138
138
  if len(split_num)<4: split_num+=[split_num[-1]]*(4-len(split_num))
139
139
  vect=[int(split_num[i]) for i in (0,2,1,3)]