psychopy 2025.1.1__py3-none-any.whl → 2025.2.1__py3-none-any.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 psychopy might be problematic. Click here for more details.
- psychopy/VERSION +1 -1
- psychopy/alerts/alertsCatalogue/4810.yaml +19 -0
- psychopy/alerts/alertsCatalogue/alertCategories.yaml +4 -0
- psychopy/alerts/alertsCatalogue/alertmsg.py +15 -1
- psychopy/alerts/alertsCatalogue/generateAlertmsg.py +2 -2
- psychopy/app/Resources/classic/add_many.png +0 -0
- psychopy/app/Resources/classic/add_many@2x.png +0 -0
- psychopy/app/Resources/classic/devices.png +0 -0
- psychopy/app/Resources/classic/devices@2x.png +0 -0
- psychopy/app/Resources/classic/photometer.png +0 -0
- psychopy/app/Resources/classic/photometer@2x.png +0 -0
- psychopy/app/Resources/dark/add_many.png +0 -0
- psychopy/app/Resources/dark/add_many@2x.png +0 -0
- psychopy/app/Resources/dark/devices.png +0 -0
- psychopy/app/Resources/dark/devices@2x.png +0 -0
- psychopy/app/Resources/dark/photometer.png +0 -0
- psychopy/app/Resources/dark/photometer@2x.png +0 -0
- psychopy/app/Resources/light/add_many.png +0 -0
- psychopy/app/Resources/light/add_many@2x.png +0 -0
- psychopy/app/Resources/light/devices.png +0 -0
- psychopy/app/Resources/light/devices@2x.png +0 -0
- psychopy/app/Resources/light/photometer.png +0 -0
- psychopy/app/Resources/light/photometer@2x.png +0 -0
- psychopy/app/_psychopyApp.py +35 -13
- psychopy/app/builder/builder.py +88 -35
- psychopy/app/builder/dialogs/__init__.py +69 -220
- psychopy/app/builder/dialogs/dlgsCode.py +29 -8
- psychopy/app/builder/dialogs/paramCtrls.py +1468 -904
- psychopy/app/builder/validators.py +25 -17
- psychopy/app/coder/coder.py +12 -1
- psychopy/app/coder/repl.py +5 -2
- psychopy/app/colorpicker/__init__.py +1 -1
- psychopy/app/deviceManager/__init__.py +1 -0
- psychopy/app/deviceManager/addDialog.py +218 -0
- psychopy/app/deviceManager/dialog.py +185 -0
- psychopy/app/deviceManager/panel.py +191 -0
- psychopy/app/deviceManager/utils.py +60 -0
- psychopy/app/idle.py +7 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +12695 -10592
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/cs_CZ/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/da_DK/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/de_DE/LC_MESSAGE/messages.po +11221 -9712
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/el_GR/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_NZ/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/en_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/en_US/LC_MESSAGE/messages.po +10195 -18
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +11924 -9103
- psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/es_US/LC_MESSAGE/messages.po +11917 -9101
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/et_EE/LC_MESSAGE/messages.po +11084 -9569
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fa_IR/LC_MESSAGE/messages.po +11590 -5806
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fi_FI/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/fr_FR/LC_MESSAGE/messages.po +11091 -9577
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/he_IL/LC_MESSAGE/messages.po +11072 -9549
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hi_IN/LC_MESSAGE/messages.po +11071 -9559
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/hu_HU/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/it_IT/LC_MESSAGE/messages.po +11072 -9560
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1485 -1137
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ko_KR/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ms_MY/LC_MESSAGE/messages.po +11463 -8757
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nl_NL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/nn_NO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pl_PL/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +11288 -9434
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ro_RO/LC_MESSAGE/messages.po +10200 -25
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/ru_RU/LC_MESSAGE/messages.po +10199 -24
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/sv_SE/LC_MESSAGE/messages.po +11441 -8747
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/tr_TR/LC_MESSAGE/messages.po +11069 -9545
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_CN/LC_MESSAGE/messages.po +12085 -8268
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.mo +0 -0
- psychopy/app/locale/zh_TW/LC_MESSAGE/messages.po +11929 -8022
- psychopy/app/plugin_manager/dialog.py +12 -3
- psychopy/app/plugin_manager/packageIndex.py +303 -0
- psychopy/app/plugin_manager/packages.py +203 -63
- psychopy/app/plugin_manager/plugins.py +120 -240
- psychopy/app/preferencesDlg.py +6 -1
- psychopy/app/psychopyApp.py +16 -4
- psychopy/app/runner/runner.py +10 -2
- psychopy/app/runner/scriptProcess.py +8 -3
- psychopy/app/stdout/stdOutRich.py +11 -4
- psychopy/app/themes/icons.py +3 -0
- psychopy/app/utils.py +61 -0
- psychopy/data/experiment.py +133 -23
- psychopy/data/routine.py +12 -0
- psychopy/data/staircase.py +42 -20
- psychopy/data/trial.py +20 -12
- psychopy/data/utils.py +42 -2
- psychopy/demos/builder/Experiments/dragAndDrop/drag_and_drop.psyexp +22 -5
- psychopy/demos/builder/Experiments/dragAndDrop/stimuli/solutions.xlsx +0 -0
- psychopy/demos/builder/Experiments/stroopVoice/stroopVoice.psyexp +2 -12
- psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +3 -8
- psychopy/demos/builder/Feature Demos/movies/movie.psyexp +220 -0
- psychopy/demos/builder/Feature Demos/movies/readme.md +3 -0
- psychopy/demos/builder/Feature Demos/visualValidator/visualValidator.psyexp +1 -2
- psychopy/demos/builder/Hardware/camera/camera.psyexp +3 -16
- psychopy/demos/builder/Hardware/microphone/microphone.psyexp +3 -16
- psychopy/demos/coder/hardware/hdf5_extract.py +133 -0
- psychopy/event.py +20 -15
- psychopy/experiment/_experiment.py +86 -10
- psychopy/experiment/components/__init__.py +3 -10
- psychopy/experiment/components/_base.py +9 -20
- psychopy/experiment/components/button/__init__.py +1 -1
- psychopy/experiment/components/buttonBox/__init__.py +50 -54
- psychopy/experiment/components/camera/__init__.py +137 -359
- psychopy/experiment/components/keyboard/__init__.py +17 -24
- psychopy/experiment/components/microphone/__init__.py +61 -110
- psychopy/experiment/components/movie/__init__.py +2 -3
- psychopy/experiment/components/serialOut/__init__.py +192 -93
- psychopy/experiment/components/settings/__init__.py +45 -27
- psychopy/experiment/components/sound/__init__.py +82 -73
- psychopy/experiment/components/soundsensor/__init__.py +43 -80
- psychopy/experiment/devices.py +303 -0
- psychopy/experiment/exports.py +20 -18
- psychopy/experiment/flow.py +7 -0
- psychopy/experiment/loops.py +47 -29
- psychopy/experiment/monitor.py +74 -0
- psychopy/experiment/params.py +48 -10
- psychopy/experiment/plugins.py +28 -108
- psychopy/experiment/py2js_transpiler.py +1 -1
- psychopy/experiment/routines/__init__.py +1 -1
- psychopy/experiment/routines/_base.py +59 -24
- psychopy/experiment/routines/audioValidator/__init__.py +19 -155
- psychopy/experiment/routines/visualValidator/__init__.py +25 -25
- psychopy/hardware/__init__.py +20 -57
- psychopy/hardware/button.py +15 -2
- psychopy/hardware/camera/__init__.py +2237 -1394
- psychopy/hardware/joystick/__init__.py +1 -1
- psychopy/hardware/keyboard.py +5 -8
- psychopy/hardware/listener.py +4 -1
- psychopy/hardware/manager.py +75 -35
- psychopy/hardware/microphone.py +52 -6
- psychopy/hardware/monitor.py +144 -0
- psychopy/hardware/photometer/__init__.py +156 -117
- psychopy/hardware/serialdevice.py +16 -2
- psychopy/hardware/soundsensor.py +4 -1
- psychopy/iohub/devices/deviceConfigValidation.py +2 -1
- psychopy/iohub/devices/keyboard/darwin.py +8 -5
- psychopy/iohub/util/__init__.py +7 -8
- psychopy/localization/generateTranslationTemplate.py +208 -116
- psychopy/localization/messages.pot +4305 -3502
- psychopy/monitors/MonitorCenter.py +174 -74
- psychopy/plugins/__init__.py +6 -4
- psychopy/preferences/devices.py +80 -0
- psychopy/preferences/generateHints.py +2 -1
- psychopy/preferences/preferences.py +35 -11
- psychopy/scripts/psychopy-pkgutil.py +969 -0
- psychopy/scripts/psyexpCompile.py +1 -1
- psychopy/session.py +34 -38
- psychopy/sound/__init__.py +6 -260
- psychopy/sound/audioclip.py +164 -0
- psychopy/sound/backend_ptb.py +8 -0
- psychopy/sound/backend_pygame.py +10 -0
- psychopy/sound/backend_pysound.py +9 -0
- psychopy/sound/backends/__init__.py +0 -0
- psychopy/sound/microphone.py +3 -0
- psychopy/sound/sound.py +58 -0
- psychopy/tests/data/correctScript/python/correctNoiseStimComponent.py +1 -1
- psychopy/tests/data/duplicateHeaders.csv +2 -0
- psychopy/tests/test_app/test_builder/test_BuilderFrame.py +22 -7
- psychopy/tests/test_app/test_builder/test_CompileFromBuilder.py +0 -2
- psychopy/tests/test_data/test_utils.py +5 -1
- psychopy/tests/test_experiment/test_components/test_ButtonBoxComponent.py +22 -2
- psychopy/tests/test_hardware/test_ports.py +0 -12
- psychopy/tests/test_tools/test_stringtools.py +1 -1
- psychopy/tools/attributetools.py +12 -5
- psychopy/tools/fontmanager.py +17 -14
- psychopy/tools/movietools.py +43 -2
- psychopy/tools/stringtools.py +33 -8
- psychopy/tools/versionchooser.py +1 -1
- psychopy/validation/audio.py +5 -1
- psychopy/validation/visual.py +5 -1
- psychopy/visual/basevisual.py +8 -7
- psychopy/visual/circle.py +2 -2
- psychopy/visual/image.py +29 -109
- psychopy/visual/movies/__init__.py +1800 -313
- psychopy/visual/polygon.py +4 -0
- psychopy/visual/shape.py +2 -2
- psychopy/visual/window.py +34 -11
- psychopy/voicekey/__init__.py +41 -669
- psychopy/voicekey/labjack_vks.py +7 -48
- psychopy/voicekey/parallel_vks.py +7 -42
- psychopy/voicekey/vk_tools.py +114 -263
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/METADATA +17 -11
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/RECORD +216 -184
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/WHEEL +1 -1
- psychopy/visual/movies/players/__init__.py +0 -62
- psychopy/visual/movies/players/ffpyplayer_player.py +0 -1401
- psychopy/voicekey/demo_vks.py +0 -12
- psychopy/voicekey/signal.py +0 -42
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/entry_points.txt +0 -0
- {psychopy-2025.1.1.dist-info → psychopy-2025.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,6 +6,8 @@ import wx
|
|
|
6
6
|
from psychopy import prefs
|
|
7
7
|
from psychopy.app import getAppInstance
|
|
8
8
|
from psychopy.app.plugin_manager import PluginManagerPanel, PackageManagerPanel, InstallStdoutPanel
|
|
9
|
+
from psychopy.app.plugin_manager.packageIndex import (
|
|
10
|
+
loadPackageIndex, refreshPackageIndex, freePackageIndex)
|
|
9
11
|
from psychopy.experiment import getAllElements
|
|
10
12
|
from psychopy.localization import _translate
|
|
11
13
|
import psychopy.logging as logging
|
|
@@ -16,7 +18,7 @@ import os
|
|
|
16
18
|
import subprocess as sp
|
|
17
19
|
import psychopy.plugins as plugins
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
# flag to indicate if PsychoPy needs to be restarted after installing a package
|
|
@@ -68,6 +70,8 @@ class EnvironmentManagerDlg(wx.Dialog):
|
|
|
68
70
|
|
|
69
71
|
self.notebook.ChangeSelection(0)
|
|
70
72
|
|
|
73
|
+
loadPackageIndex()
|
|
74
|
+
|
|
71
75
|
@staticmethod
|
|
72
76
|
def getPackageVersionInfo(packageName):
|
|
73
77
|
"""Query packages for available versions.
|
|
@@ -231,7 +235,7 @@ class EnvironmentManagerDlg(wx.Dialog):
|
|
|
231
235
|
# if forceReinstall is None, work out from version
|
|
232
236
|
if forceReinstall is None:
|
|
233
237
|
forceReinstall = version is not None
|
|
234
|
-
# use package tools to install
|
|
238
|
+
# use package tools to install
|
|
235
239
|
self.pipProcess = pkgtools.installPackage(
|
|
236
240
|
packageName,
|
|
237
241
|
upgrade=version is None,
|
|
@@ -349,8 +353,10 @@ class EnvironmentManagerDlg(wx.Dialog):
|
|
|
349
353
|
# clear pip process
|
|
350
354
|
self.pipProcess = None
|
|
351
355
|
# refresh view
|
|
352
|
-
|
|
356
|
+
refreshPackageIndex()
|
|
357
|
+
loadPackageIndex()
|
|
353
358
|
self.pluginMgr.updateInfo()
|
|
359
|
+
self.packageMgr.refresh()
|
|
354
360
|
|
|
355
361
|
def onUninstallExit(self, pid, exitCode):
|
|
356
362
|
# write installation termination statement
|
|
@@ -375,6 +381,9 @@ class EnvironmentManagerDlg(wx.Dialog):
|
|
|
375
381
|
# if they change their mind, cancel closing
|
|
376
382
|
if dlg.ShowModal() == wx.ID_NO:
|
|
377
383
|
return
|
|
384
|
+
|
|
385
|
+
# free package index
|
|
386
|
+
freePackageIndex()
|
|
378
387
|
|
|
379
388
|
if evt is not None:
|
|
380
389
|
evt.Skip()
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# get script path
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from psychopy import logging
|
|
5
|
+
from psychopy import prefs
|
|
6
|
+
import sys
|
|
7
|
+
import subprocess as sp
|
|
8
|
+
import os
|
|
9
|
+
import json
|
|
10
|
+
import wx
|
|
11
|
+
|
|
12
|
+
_packageIndex = None
|
|
13
|
+
_isIndexing = True # Flag to indicate if the package index is being updated
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def refreshPackageIndex(fetch=False):
|
|
17
|
+
"""Refresh the package index.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
fetch : bool, optional
|
|
22
|
+
If True, fetch the latest package index from the remote server
|
|
23
|
+
regardless of whether it is already present on disk. The default is
|
|
24
|
+
False, which means it will only update if the index is not present or is
|
|
25
|
+
outdated.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
global _isIndexing
|
|
29
|
+
_isIndexing = True
|
|
30
|
+
scriptDir = prefs.paths['scripts']
|
|
31
|
+
|
|
32
|
+
# Construct the command to run the script
|
|
33
|
+
_cmd = [
|
|
34
|
+
sys.executable,
|
|
35
|
+
os.path.normpath(os.path.join(scriptDir, 'psychopy-pkgutil.py')),
|
|
36
|
+
'--app-pref-dir', prefs.paths['userPrefsDir'],
|
|
37
|
+
'update']
|
|
38
|
+
_cmd += ['--fetch'] if fetch else []
|
|
39
|
+
|
|
40
|
+
# Execute the command
|
|
41
|
+
try:
|
|
42
|
+
headerText = ' Updating package index '
|
|
43
|
+
headerText = headerText.center(80, '=')
|
|
44
|
+
print(headerText)
|
|
45
|
+
|
|
46
|
+
env = os.environ.copy()
|
|
47
|
+
print(f"Running command: {' '.join(_cmd)}")
|
|
48
|
+
|
|
49
|
+
proc = sp.Popen(_cmd,
|
|
50
|
+
stdout=sp.PIPE,
|
|
51
|
+
stderr=sp.PIPE,
|
|
52
|
+
env=env,
|
|
53
|
+
universal_newlines=True)
|
|
54
|
+
_, error = proc.communicate()
|
|
55
|
+
if proc.returncode != 0:
|
|
56
|
+
return
|
|
57
|
+
if error:
|
|
58
|
+
logging.error(f"Error refreshing package index: {error}")
|
|
59
|
+
print(f"Error: {error}")
|
|
60
|
+
|
|
61
|
+
logging.info("Package index refreshed successfully.")
|
|
62
|
+
except sp.CalledProcessError as e:
|
|
63
|
+
logging.error(f"Error refreshing package index: {e}")
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
logging.error("The script was not found. Please check the script path.")
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logging.error(f"An unexpected error occurred: {e}")
|
|
68
|
+
finally:
|
|
69
|
+
_isIndexing = False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def isIndexing():
|
|
73
|
+
"""Check if the package index is currently being updated.
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
bool
|
|
78
|
+
True if the package index is being updated, False otherwise. This is to
|
|
79
|
+
check if the package index is currently being refreshed from another
|
|
80
|
+
thread.
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
global _isIndexing
|
|
84
|
+
return _isIndexing
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def downloadPluginAssets(fetch=False):
|
|
88
|
+
"""Download assets for the specified plugin.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
fetch : bool, optional
|
|
93
|
+
If True, fetch the plugin assets even if present on disk.
|
|
94
|
+
The default is False.
|
|
95
|
+
|
|
96
|
+
"""
|
|
97
|
+
global _packageIndex
|
|
98
|
+
if _packageIndex is None:
|
|
99
|
+
loadPackageIndex()
|
|
100
|
+
|
|
101
|
+
# get all plugin icon URLs
|
|
102
|
+
pluginIconsURLs = []
|
|
103
|
+
for plugin in _packageIndex['available']['plugins']['packages'].values():
|
|
104
|
+
pluginIcon = plugin.get('icon', None)
|
|
105
|
+
if pluginIcon is not None:
|
|
106
|
+
pluginIconsURLs.append(pluginIcon)
|
|
107
|
+
|
|
108
|
+
# cache directory for the plugin icons
|
|
109
|
+
appPluginCacheDir = os.path.join(
|
|
110
|
+
prefs.paths['userCacheDir'], 'appCache', 'plugins')
|
|
111
|
+
|
|
112
|
+
# make sure we have a directory to put these files in
|
|
113
|
+
try:
|
|
114
|
+
os.makedirs(appPluginCacheDir, exist_ok=True)
|
|
115
|
+
except OSError as err:
|
|
116
|
+
if err.errno != os.errno.EEXIST:
|
|
117
|
+
logging.error(f"Error creating directory {appPluginCacheDir}: {err}")
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
headerText = ' Downloading plugin icons '
|
|
121
|
+
headerText = headerText.center(80, '=')
|
|
122
|
+
print(headerText)
|
|
123
|
+
|
|
124
|
+
for iconUrl in pluginIconsURLs:
|
|
125
|
+
# get the icon file name from URL
|
|
126
|
+
iconFileName = os.path.basename(iconUrl)
|
|
127
|
+
# get the icon file path
|
|
128
|
+
iconPath = os.path.join(appPluginCacheDir, iconFileName)
|
|
129
|
+
|
|
130
|
+
# check if we already have a copy of the icon
|
|
131
|
+
if os.path.exists(iconPath) and not fetch:
|
|
132
|
+
print(f"Plugin icon already exists at {iconPath}")
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
print(f"Downloading plugin icon from {iconUrl} to {iconPath}")
|
|
136
|
+
|
|
137
|
+
import requests
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
# Make a GET request to download the file
|
|
141
|
+
response = requests.get(iconUrl, stream=True)
|
|
142
|
+
response.raise_for_status() # Raise an error for bad responses
|
|
143
|
+
|
|
144
|
+
# Open the local file in write-binary mode and save the content
|
|
145
|
+
with open(iconPath, 'wb') as file:
|
|
146
|
+
for chunk in response.iter_content(chunk_size=8192):
|
|
147
|
+
file.write(chunk)
|
|
148
|
+
wx.YieldIfNeeded()
|
|
149
|
+
|
|
150
|
+
print(f"Plugin icon downloaded successfully to {iconPath}")
|
|
151
|
+
except requests.exceptions.RequestException as e:
|
|
152
|
+
logging.error(f"Error downloading plugin icon: {e}")
|
|
153
|
+
except IOError as e:
|
|
154
|
+
logging.error(f"Error saving plugin icon: {e}")
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logging.error(f"An unexpected error occurred: {e}")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def loadPackageIndex():
|
|
160
|
+
"""Load the package index from the specified file.
|
|
161
|
+
"""
|
|
162
|
+
global _packageIndex
|
|
163
|
+
try:
|
|
164
|
+
# Load the package index from the specified file
|
|
165
|
+
packageIndexPath = os.path.join(
|
|
166
|
+
prefs.paths['userPrefsDir'], 'cache', 'appCache', 'psychopy_packages.json')
|
|
167
|
+
with open(packageIndexPath, 'r') as f:
|
|
168
|
+
indexData = f.read()
|
|
169
|
+
_packageIndex = json.loads(indexData)
|
|
170
|
+
except FileNotFoundError:
|
|
171
|
+
logging.error("Package index file not found.")
|
|
172
|
+
except json.JSONDecodeError:
|
|
173
|
+
logging.error("Error decoding package index JSON.")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def freePackageIndex():
|
|
177
|
+
"""Free the package index to allow it to be reloaded.
|
|
178
|
+
"""
|
|
179
|
+
global _packageIndex
|
|
180
|
+
_packageIndex = None
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def getInstalledPackages():
|
|
184
|
+
"""Get the list of installed packages from the package index.
|
|
185
|
+
"""
|
|
186
|
+
global _packageIndex
|
|
187
|
+
if _packageIndex is None:
|
|
188
|
+
loadPackageIndex()
|
|
189
|
+
|
|
190
|
+
return _packageIndex['installed'] if _packageIndex else {}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def getRemotePackages():
|
|
194
|
+
"""Get the list of remote packages from the package index.
|
|
195
|
+
"""
|
|
196
|
+
global _packageIndex
|
|
197
|
+
if _packageIndex is None:
|
|
198
|
+
loadPackageIndex()
|
|
199
|
+
|
|
200
|
+
return _packageIndex['available']['remote']['PyPI'] if _packageIndex else []
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def getPluginPackages(asList=True):
|
|
204
|
+
"""Get the list of plugin packages from the package index.
|
|
205
|
+
"""
|
|
206
|
+
global _packageIndex
|
|
207
|
+
if _packageIndex is None:
|
|
208
|
+
loadPackageIndex()
|
|
209
|
+
|
|
210
|
+
if asList: # legacy
|
|
211
|
+
return list(_packageIndex['available']['plugins']['packages'].values())
|
|
212
|
+
|
|
213
|
+
return _packageIndex['available']['plugins']['packages'] if _packageIndex else {}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def isPackageInstalled(packageName):
|
|
217
|
+
"""Check if a package is installed.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
tuple
|
|
222
|
+
A tuple containing a boolean indicating if the package is installed,
|
|
223
|
+
and its version if installed.
|
|
224
|
+
If the package is not installed, the version will be None.
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
global _packageIndex
|
|
228
|
+
if _packageIndex is None:
|
|
229
|
+
loadPackageIndex()
|
|
230
|
+
|
|
231
|
+
if isUserPackageInstalled(packageName):
|
|
232
|
+
state = 'u'
|
|
233
|
+
elif isSystemPackageInstalled(packageName):
|
|
234
|
+
state = 's'
|
|
235
|
+
elif packageName in getRemotePackages():
|
|
236
|
+
state = 'n'
|
|
237
|
+
else:
|
|
238
|
+
state = None
|
|
239
|
+
|
|
240
|
+
if state is not None:
|
|
241
|
+
version = None
|
|
242
|
+
else:
|
|
243
|
+
version = None
|
|
244
|
+
|
|
245
|
+
return state, version
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def isSystemPackageInstalled(packageName):
|
|
249
|
+
"""Check if a package is installed in the system directory.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
bool
|
|
254
|
+
True if the package is installed in the system directory, False otherwise.
|
|
255
|
+
|
|
256
|
+
"""
|
|
257
|
+
global _packageIndex
|
|
258
|
+
if _packageIndex is None:
|
|
259
|
+
loadPackageIndex()
|
|
260
|
+
|
|
261
|
+
# Check if the package is in the system packages list
|
|
262
|
+
return packageName in _packageIndex['installed']['system']['packages'].keys()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def isUserPackageInstalled(packageName):
|
|
266
|
+
"""Check if a package is installed in the user directory.
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
bool
|
|
271
|
+
True if the package is installed in the user directory, False otherwise.
|
|
272
|
+
|
|
273
|
+
"""
|
|
274
|
+
global _packageIndex
|
|
275
|
+
if _packageIndex is None:
|
|
276
|
+
loadPackageIndex()
|
|
277
|
+
|
|
278
|
+
print(list(_packageIndex['installed']['user']['packages'].keys()))
|
|
279
|
+
|
|
280
|
+
# Check if the package is in the user packages list
|
|
281
|
+
return packageName in _packageIndex['installed']['user']['packages'].keys()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def getAvailablePackages():
|
|
285
|
+
"""Get the list of available packages from the package index.
|
|
286
|
+
"""
|
|
287
|
+
global _packageIndex
|
|
288
|
+
if _packageIndex is None:
|
|
289
|
+
loadPackageIndex()
|
|
290
|
+
|
|
291
|
+
return _packageIndex['available']['PyPI']['packages'] if _packageIndex else {}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def refreshPackageIndexTask(app=None):
|
|
295
|
+
"""
|
|
296
|
+
Run the refreshPackageIndex.py script to update the package index.
|
|
297
|
+
"""
|
|
298
|
+
refreshPackageIndex()
|
|
299
|
+
downloadPluginAssets()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
if __name__ == "__main__":
|
|
303
|
+
pass
|