psychopy 2024.1.4__py3-none-any.whl → 2024.2.0__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.

Files changed (325) hide show
  1. psychopy/.DS_Store +0 -0
  2. psychopy/CHANGELOG.txt +206 -0
  3. psychopy/GIT_SHA +1 -0
  4. psychopy/VERSION +1 -0
  5. psychopy/__init__.py +77 -15
  6. psychopy/app/Resources/classic/plugin16.png +0 -0
  7. psychopy/app/Resources/classic/plugin16@2x.png +0 -0
  8. psychopy/app/Resources/dark/plugin16.png +0 -0
  9. psychopy/app/Resources/dark/plugin16@2x.png +0 -0
  10. psychopy/app/Resources/light/plugin16.png +0 -0
  11. psychopy/app/Resources/light/plugin16@2x.png +0 -0
  12. psychopy/app/__init__.py +76 -2
  13. psychopy/app/_psychopyApp.py +126 -101
  14. psychopy/app/builder/builder.py +14 -10
  15. psychopy/app/builder/dialogs/__init__.py +8 -8
  16. psychopy/app/builder/dialogs/dlgsConditions.py +12 -13
  17. psychopy/app/builder/dialogs/paramCtrls.py +24 -57
  18. psychopy/app/builder/validators.py +2 -2
  19. psychopy/app/coder/codeEditorBase.py +8 -8
  20. psychopy/app/coder/coder.py +4 -4
  21. psychopy/app/connections/sendusage.py +2 -2
  22. psychopy/app/connections/updates.py +9 -9
  23. psychopy/app/dialogs.py +34 -2
  24. psychopy/app/idle.py +31 -0
  25. psychopy/app/jobs.py +21 -3
  26. psychopy/app/linuxconfig/__init__.py +9 -0
  27. psychopy/app/locale/ar_001/LC_MESSAGE/messages.mo +0 -0
  28. psychopy/app/locale/ar_001/LC_MESSAGE/messages.po +4602 -2540
  29. psychopy/app/locale/es_CO/LC_MESSAGE/messages.mo +0 -0
  30. psychopy/app/locale/es_CO/LC_MESSAGE/messages.po +56 -54
  31. psychopy/app/locale/es_ES/LC_MESSAGE/messages.po +53 -43
  32. psychopy/app/locale/es_US/LC_MESSAGE/messages.mo +0 -0
  33. psychopy/app/locale/es_US/LC_MESSAGE/messages.po +56 -54
  34. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.mo +0 -0
  35. psychopy/app/locale/ja_JP/LC_MESSAGE/messages.po +1011 -942
  36. psychopy/app/locale/pt_PT/LC_MESSAGE/messages.po +9415 -5
  37. psychopy/app/pavlovia_ui/_base.py +33 -3
  38. psychopy/app/pavlovia_ui/search.py +0 -1
  39. psychopy/app/plugin_manager/dialog.py +104 -51
  40. psychopy/app/plugin_manager/packages.py +5 -0
  41. psychopy/app/plugin_manager/plugins.py +145 -67
  42. psychopy/app/preferencesDlg.py +8 -8
  43. psychopy/app/psychopyApp.py +11 -5
  44. psychopy/app/ribbon.py +124 -14
  45. psychopy/app/runner/runner.py +6 -1
  46. psychopy/app/stdout/stdOutRich.py +27 -11
  47. psychopy/app/themes/icons.py +52 -2
  48. psychopy/assets/__init__.py +0 -0
  49. psychopy/assets/click.png +0 -0
  50. psychopy/assets/clicknext.png +0 -0
  51. psychopy/assets/next.png +0 -0
  52. psychopy/assets/psychopy.ico +0 -0
  53. psychopy/assets/psychopy.png +0 -0
  54. psychopy/assets/templates/__init__.py +0 -0
  55. psychopy/assets/touch.png +0 -0
  56. psychopy/assets/touchnext.png +0 -0
  57. psychopy/assets/window.ico +0 -0
  58. psychopy/changes/2023.1.0.md +9 -0
  59. psychopy/changes/2024.1.0.md +16 -0
  60. psychopy/changes/__init__.py +0 -0
  61. psychopy/clock.py +2 -2
  62. psychopy/colors.py +2 -1
  63. psychopy/compatibility.py +53 -1
  64. psychopy/contrib/.DS_Store +0 -0
  65. psychopy/contrib/configobj/__init__.py +10 -8
  66. psychopy/data/__init__.py +3 -2
  67. psychopy/data/base.py +5 -5
  68. psychopy/data/experiment.py +130 -4
  69. psychopy/data/routine.py +56 -0
  70. psychopy/data/staircase.py +2 -2
  71. psychopy/data/trial.py +559 -97
  72. psychopy/data/utils.py +56 -21
  73. psychopy/demos/.DS_Store +0 -0
  74. psychopy/demos/builder/.DS_Store +0 -0
  75. psychopy/demos/builder/Design Templates/.DS_Store +0 -0
  76. psychopy/demos/builder/Experiments/.DS_Store +0 -0
  77. psychopy/demos/builder/Feature Demos/.DS_Store +0 -0
  78. psychopy/demos/builder/Feature Demos/buttonBox/buttonBoxDemo.psyexp +375 -0
  79. psychopy/demos/builder/Feature Demos/buttonBox/readme.md +5 -0
  80. psychopy/demos/builder/Feature Demos/pilotMode/pilotMode.psyexp +433 -0
  81. psychopy/demos/builder/Feature Demos/pilotMode/readme.md +7 -0
  82. psychopy/demos/builder/Hardware/.DS_Store +0 -0
  83. psychopy/demos/builder/Helper Tools/.DS_Store +0 -0
  84. psychopy/demos/coder/.DS_Store +0 -0
  85. psychopy/demos/coder/hardware/testSoundLatency.py +2 -2
  86. psychopy/demos/coder/iohub/.DS_Store +0 -0
  87. psychopy/demos/coder/misc/hdf5_2_csv +33 -0
  88. psychopy/event.py +30 -29
  89. psychopy/experiment/.DS_Store +0 -0
  90. psychopy/experiment/_experiment.py +6 -6
  91. psychopy/experiment/components/.DS_Store +0 -0
  92. psychopy/experiment/components/__init__.py +6 -3
  93. psychopy/experiment/components/_base.py +286 -131
  94. psychopy/experiment/components/aperture/.DS_Store +0 -0
  95. psychopy/experiment/components/brush/.DS_Store +0 -0
  96. psychopy/experiment/components/button/.DS_Store +0 -0
  97. psychopy/experiment/components/button/__init__.py +5 -1
  98. psychopy/experiment/components/buttonBox/.DS_Store +0 -0
  99. psychopy/experiment/components/camera/.DS_Store +0 -0
  100. psychopy/experiment/components/code/.DS_Store +0 -0
  101. psychopy/experiment/components/dots/.DS_Store +0 -0
  102. psychopy/experiment/components/eyetracker_record/.DS_Store +0 -0
  103. psychopy/experiment/components/eyetracker_record/__init__.py +92 -30
  104. psychopy/experiment/components/form/.DS_Store +0 -0
  105. psychopy/experiment/components/form/__init__.py +6 -2
  106. psychopy/experiment/components/grating/.DS_Store +0 -0
  107. psychopy/experiment/components/grating/__init__.py +14 -3
  108. psychopy/experiment/components/image/.DS_Store +0 -0
  109. psychopy/experiment/components/image/__init__.py +14 -3
  110. psychopy/experiment/components/joyButtons/.DS_Store +0 -0
  111. psychopy/experiment/components/joystick/.DS_Store +0 -0
  112. psychopy/experiment/components/keyboard/.DS_Store +0 -0
  113. psychopy/experiment/components/keyboard/__init__.py +22 -10
  114. psychopy/experiment/components/microphone/.DS_Store +0 -0
  115. psychopy/experiment/components/microphone/__init__.py +59 -39
  116. psychopy/experiment/components/mouse/.DS_Store +0 -0
  117. psychopy/experiment/components/mouse/__init__.py +44 -29
  118. psychopy/experiment/components/movie/.DS_Store +0 -0
  119. psychopy/experiment/components/movie/__init__.py +1 -1
  120. psychopy/experiment/components/panorama/.DS_Store +0 -0
  121. psychopy/experiment/components/parallelOut/.DS_Store +0 -0
  122. psychopy/experiment/components/patch/.DS_Store +0 -0
  123. psychopy/experiment/components/polygon/.DS_Store +0 -0
  124. psychopy/experiment/components/polygon/__init__.py +26 -6
  125. psychopy/experiment/components/progress/.DS_Store +0 -0
  126. psychopy/experiment/components/ratingScale/.DS_Store +0 -0
  127. psychopy/experiment/components/resourceManager/.DS_Store +0 -0
  128. psychopy/experiment/components/roi/.DS_Store +0 -0
  129. psychopy/experiment/components/roi/__init__.py +5 -0
  130. psychopy/experiment/components/routineSettings/.DS_Store +0 -0
  131. psychopy/experiment/components/routineSettings/__init__.py +57 -10
  132. psychopy/experiment/components/serialOut/.DS_Store +0 -0
  133. psychopy/experiment/components/settings/.DS_Store +0 -0
  134. psychopy/experiment/components/settings/__init__.py +117 -42
  135. psychopy/experiment/components/slider/.DS_Store +0 -0
  136. psychopy/experiment/components/sound/.DS_Store +0 -0
  137. psychopy/experiment/components/sound/__init__.py +54 -19
  138. psychopy/experiment/components/static/.DS_Store +0 -0
  139. psychopy/experiment/components/static/__init__.py +1 -1
  140. psychopy/experiment/components/text/.DS_Store +0 -0
  141. psychopy/experiment/components/text/__init__.py +28 -3
  142. psychopy/experiment/components/textbox/.DS_Store +0 -0
  143. psychopy/experiment/components/textbox/__init__.py +12 -2
  144. psychopy/experiment/components/unknown/.DS_Store +0 -0
  145. psychopy/experiment/components/unknown/__init__.py +1 -2
  146. psychopy/experiment/components/unknownPlugin/.DS_Store +0 -0
  147. psychopy/experiment/components/unknownPlugin/__init__.py +2 -2
  148. psychopy/experiment/components/variable/.DS_Store +0 -0
  149. psychopy/experiment/flow.py +11 -4
  150. psychopy/experiment/loops.py +85 -37
  151. psychopy/experiment/params.py +74 -32
  152. psychopy/experiment/py2js_transpiler.py +8 -1
  153. psychopy/experiment/routines/.DS_Store +0 -0
  154. psychopy/experiment/routines/_base.py +102 -22
  155. psychopy/experiment/routines/counterbalance/.DS_Store +0 -0
  156. psychopy/experiment/routines/counterbalance/__init__.py +5 -1
  157. psychopy/experiment/routines/eyetracker_calibrate/.DS_Store +0 -0
  158. psychopy/experiment/routines/eyetracker_validate/.DS_Store +0 -0
  159. psychopy/experiment/routines/pavlovia_survey/.DS_Store +0 -0
  160. psychopy/experiment/routines/photodiodeValidator/.DS_Store +0 -0
  161. psychopy/experiment/routines/photodiodeValidator/__init__.py +6 -5
  162. psychopy/experiment/routines/unknown/.DS_Store +0 -0
  163. psychopy/gui/wxgui.py +4 -4
  164. psychopy/hardware/.DS_Store +0 -0
  165. psychopy/hardware/__init__.py +1 -1
  166. psychopy/hardware/base.py +12 -0
  167. psychopy/hardware/camera/__init__.py +1 -15
  168. psychopy/hardware/cedrus.py +10 -11
  169. psychopy/hardware/crs/colorcal.py +13 -22
  170. psychopy/hardware/crs/optical.py +10 -20
  171. psychopy/hardware/emulator.py +17 -14
  172. psychopy/hardware/eyetracker.py +42 -118
  173. psychopy/hardware/gammasci.py +4 -15
  174. psychopy/hardware/keyboard.py +102 -10
  175. psychopy/hardware/listener.py +3 -0
  176. psychopy/hardware/microphone.py +148 -18
  177. psychopy/hardware/minolta.py +8 -15
  178. psychopy/hardware/photodiode.py +191 -16
  179. psychopy/hardware/photometer/__init__.py +11 -19
  180. psychopy/hardware/pr.py +8 -15
  181. psychopy/hardware/speaker.py +39 -4
  182. psychopy/info.py +0 -71
  183. psychopy/iohub/.DS_Store +0 -0
  184. psychopy/iohub/__init__.py +1 -1
  185. psychopy/iohub/client/__init__.py +30 -20
  186. psychopy/iohub/client/keyboard.py +24 -24
  187. psychopy/iohub/datastore/__init__.py +2 -2
  188. psychopy/iohub/datastore/util.py +2 -2
  189. psychopy/iohub/default_config.yaml +1 -1
  190. psychopy/iohub/devices/.DS_Store +0 -0
  191. psychopy/iohub/devices/__init__.py +112 -25
  192. psychopy/iohub/devices/deviceConfigValidation.py +2 -1
  193. psychopy/iohub/devices/experiment/default_experiment.yaml +12 -1
  194. psychopy/iohub/devices/experiment/supported_config_settings.yaml +5 -1
  195. psychopy/iohub/devices/eyetracker/.DS_Store +0 -0
  196. psychopy/iohub/devices/eyetracker/__init__.py +46 -0
  197. psychopy/iohub/devices/eyetracker/calibration/procedure.py +2 -2
  198. psychopy/iohub/devices/eyetracker/hw/gazepoint/__init__.py +14 -2
  199. psychopy/iohub/devices/eyetracker/hw/mouse/eyetracker.py +3 -4
  200. psychopy/iohub/server.py +2 -2
  201. psychopy/iohub/start_iohub_process.py +3 -0
  202. psychopy/iohub/util/__init__.py +62 -70
  203. psychopy/layout.py +5 -5
  204. psychopy/logging.py +8 -1
  205. psychopy/microphone.py +10 -37
  206. psychopy/platform_specific/__init__.py +0 -2
  207. psychopy/platform_specific/darwin.py +1 -3
  208. psychopy/platform_specific/linux.py +31 -33
  209. psychopy/platform_specific/win32.py +38 -13
  210. psychopy/plugins/__init__.py +148 -116
  211. psychopy/plugins/util.py +39 -0
  212. psychopy/preferences/Darwin.spec +4 -2
  213. psychopy/preferences/FreeBSD.spec +4 -2
  214. psychopy/preferences/Linux.spec +4 -2
  215. psychopy/preferences/Windows.spec +4 -2
  216. psychopy/preferences/baseNoArch.spec +4 -2
  217. psychopy/preferences/preferences.py +47 -24
  218. psychopy/projects/pavlovia.py +47 -4
  219. psychopy/scripts/psyexpCompile.py +0 -4
  220. psychopy/session.py +153 -21
  221. psychopy/sound/__init__.py +31 -21
  222. psychopy/sound/_base.py +20 -3
  223. psychopy/sound/audioclip.py +320 -33
  224. psychopy/sound/backend_ptb.py +47 -58
  225. psychopy/sound/backend_pygame.py +1 -1
  226. psychopy/sound/backend_pysound.py +6 -15
  227. psychopy/sound/transcribe.py +53 -0
  228. psychopy/tests/.DS_Store +0 -0
  229. psychopy/tests/data/.DS_Store +0 -0
  230. psychopy/tests/data/TestUnknownPluginComponent_load_resave.psyexp +135 -0
  231. psychopy/tests/data/Test_textbox/test_ori_0_bottom right.png +0 -0
  232. psychopy/tests/data/Test_textbox/test_ori_0_center.png +0 -0
  233. psychopy/tests/data/Test_textbox/test_ori_0_top left.png +0 -0
  234. psychopy/tests/data/Test_textbox/test_ori_120_bottom right.png +0 -0
  235. psychopy/tests/data/Test_textbox/test_ori_120_center.png +0 -0
  236. psychopy/tests/data/Test_textbox/test_ori_120_top left.png +0 -0
  237. psychopy/tests/data/Test_textbox/test_ori_180_bottom right.png +0 -0
  238. psychopy/tests/data/Test_textbox/test_ori_180_center.png +0 -0
  239. psychopy/tests/data/Test_textbox/test_ori_180_top left.png +0 -0
  240. psychopy/tests/data/Test_textbox/test_ori_240_bottom right.png +0 -0
  241. psychopy/tests/data/Test_textbox/test_ori_240_center.png +0 -0
  242. psychopy/tests/data/Test_textbox/test_ori_240_top left.png +0 -0
  243. psychopy/tests/data/correctScript/.DS_Store +0 -0
  244. psychopy/tests/data/test_components/testClearKeyboard/testClearKeyboard.psyexp +200 -0
  245. psychopy/tests/data/test_session/.DS_Store +0 -0
  246. psychopy/tests/data/test_session/root/testFutureTrials/testFutureTrials.psyexp +155 -0
  247. psychopy/tests/data/test_session/root/testTrialNav/trialNav.psyexp +158 -0
  248. psychopy/tests/test_app/.DS_Store +0 -0
  249. psychopy/tests/test_app/conftest.py +2 -2
  250. psychopy/tests/test_app/test_speed.py +4 -1
  251. psychopy/tests/test_data/test_TrialHandler2.py +146 -1
  252. psychopy/tests/test_experiment/.DS_Store +0 -0
  253. psychopy/tests/test_experiment/needs_wx/genComponsTemplate.py +3 -3
  254. psychopy/tests/test_experiment/needs_wx/test_components.py +2 -2
  255. psychopy/tests/test_experiment/test_components/test_KeyboardComponent.py +28 -0
  256. psychopy/tests/test_experiment/test_components/test_UnknownPluginComponent.py +27 -0
  257. psychopy/tests/test_experiment/test_components/test_base_components.py +58 -0
  258. psychopy/tests/test_experiment/test_py2js.py +1 -1
  259. psychopy/tests/test_hardware/test_keyboard.py +31 -0
  260. psychopy/tests/test_hardware/test_ports.py +1 -11
  261. psychopy/tests/test_liaison/test_Liaison.py +47 -0
  262. psychopy/tests/test_misc/test_core.py +5 -0
  263. psychopy/tests/test_session/test_Session.py +5 -1
  264. psychopy/tests/test_tools/test_versionchooser.py +39 -8
  265. psychopy/tests/test_visual/test_all_stimuli.py +0 -97
  266. psychopy/tests/test_visual/test_image.py +6 -5
  267. psychopy/tests/test_visual/test_textbox.py +36 -0
  268. psychopy/tests/utils.py +4 -0
  269. psychopy/tools/filetools.py +1 -1
  270. psychopy/tools/pkgtools.py +160 -137
  271. psychopy/tools/versionchooser.py +10 -10
  272. psychopy/tools/wizard.py +3 -3
  273. psychopy/visual/.DS_Store +0 -0
  274. psychopy/visual/backends/pygletbackend.py +24 -13
  275. psychopy/visual/basevisual.py +5 -11
  276. psychopy/visual/button.py +2 -14
  277. psychopy/visual/helpers.py +5 -5
  278. psychopy/visual/line.py +1 -2
  279. psychopy/visual/movie2.py +7 -816
  280. psychopy/visual/movie3.py +7 -589
  281. psychopy/visual/movies/__init__.py +8 -11
  282. psychopy/visual/movies/frame.py +5 -2
  283. psychopy/visual/movies/players/ffpyplayer_player.py +5 -2
  284. psychopy/visual/noise.py +8 -7
  285. psychopy/visual/patch.py +7 -16
  286. psychopy/visual/radial.py +9 -7
  287. psychopy/visual/ratingscale.py +8 -1415
  288. psychopy/visual/secondorder.py +10 -9
  289. psychopy/visual/shape.py +7 -2
  290. psychopy/visual/text.py +1 -1
  291. psychopy/visual/textbox2/textbox2.py +28 -5
  292. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/METADATA +8 -13
  293. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/RECORD +307 -213
  294. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/WHEEL +1 -1
  295. psychopy/app/Resources/click.png +0 -0
  296. psychopy/app/Resources/next.png +0 -0
  297. psychopy/experiment/components/patch/__init__.py +0 -121
  298. psychopy/experiment/components/patch/classic/patch.png +0 -0
  299. psychopy/experiment/components/patch/dark/patch.png +0 -0
  300. psychopy/experiment/components/patch/dark/patch@2x.png +0 -0
  301. psychopy/experiment/components/patch/light/patch.png +0 -0
  302. psychopy/experiment/components/patch/light/patch@2x.png +0 -0
  303. psychopy/experiment/components/ratingScale/__init__.py +0 -337
  304. psychopy/experiment/components/ratingScale/classic/ratingscale.png +0 -0
  305. psychopy/experiment/components/ratingScale/classic/ratingscale@2x.png +0 -0
  306. psychopy/experiment/components/ratingScale/dark/ratingScale@2x.png +0 -0
  307. psychopy/experiment/components/ratingScale/dark/ratingscale.png +0 -0
  308. psychopy/experiment/components/ratingScale/light/ratingScale@2x.png +0 -0
  309. psychopy/experiment/components/ratingScale/light/ratingscale.png +0 -0
  310. psychopy/platform_specific/posix.py +0 -16
  311. psychopy/tests/test_sound/test_microphone.py +0 -217
  312. psychopy/tests/test_visual/test_ratingScale.py +0 -299
  313. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@16w.png +0 -0
  314. /psychopy/{app/Resources → assets}/Psychopy Window Favicon@32w.png +0 -0
  315. /psychopy/{app/Resources → assets}/USB-C.png +0 -0
  316. /psychopy/{app/Resources → assets}/USB.png +0 -0
  317. /psychopy/{app/Resources → assets}/creditCard.png +0 -0
  318. /psychopy/{app/Resources → assets}/default.mp3 +0 -0
  319. /psychopy/{app/Resources → assets}/default.mp4 +0 -0
  320. /psychopy/{app/Resources → assets}/default.png +0 -0
  321. /psychopy/{app/Resources → assets/templates}/instruct1.png +0 -0
  322. /psychopy/{app/Resources → assets/templates}/instruct2.png +0 -0
  323. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/entry_points.txt +0 -0
  324. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/AUTHORS.md +0 -0
  325. {psychopy-2024.1.4.dist-info → psychopy-2024.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,32 @@
1
1
  from psychopy.hardware import BaseDevice
2
- from psychopy.sound import setDevice, getDevices
2
+ from psychopy.sound import setDevice, getDevices, backend
3
+ from psychopy import logging
3
4
 
4
5
 
5
6
  class SpeakerDevice(BaseDevice):
6
7
  def __init__(self, index):
7
- # use first device if index is default
8
+ profiles = self.getAvailableDevices()
9
+
10
+ # if index is default (-1), setup a default device index
8
11
  if not isinstance(index, (int, float)) or index < 0:
9
- profiles = self.getAvailableDevices()
10
- index = profiles[0]['index']
12
+ index = profiles[0]['index'] # initialize as the first device
13
+
14
+ # check if a default device is already set and update index
15
+ if hasattr(backend, 'defaultOutput'):
16
+ defaultDevice = backend.defaultOutput
17
+ if isinstance(defaultDevice, (int, float)):
18
+ # if a default device index is set, use it
19
+ index = defaultDevice
20
+ elif isinstance(defaultDevice, str):
21
+ # if a default device is set by name, find it
22
+ for profile in profiles:
23
+ if profile['deviceName'] == defaultDevice:
24
+ index = profile['index']
25
+
26
+ available_index = [profile['index'] for profile in profiles]
27
+ if index < 0 or index not in available_index:
28
+ logging.error("No speaker device found with index %d" % index)
29
+
11
30
  # store index
12
31
  self.index = index
13
32
  # set global device (best we can do for now)
@@ -40,6 +59,22 @@ class SpeakerDevice(BaseDevice):
40
59
 
41
60
  return self.index == index
42
61
 
62
+ def testDevice(self):
63
+ """
64
+ Play a simple sound to check whether this device is working.
65
+ """
66
+ from psychopy.sound import Sound
67
+ import time
68
+ # create a basic sound
69
+ snd = Sound(
70
+ speaker=self.index,
71
+ value="A"
72
+ )
73
+ # play the sound for 1s
74
+ snd.play()
75
+ time.sleep(1)
76
+ snd.stop()
77
+
43
78
  @staticmethod
44
79
  def getAvailableDevices():
45
80
  devices = []
psychopy/info.py CHANGED
@@ -650,77 +650,6 @@ def _getHashGitHead(gdir='.'):
650
650
  return '(unknown branch)'
651
651
 
652
652
 
653
- def _getSvnVersion(filename):
654
- """Tries to discover the svn version (revision #) for a file.
655
-
656
- Not thoroughly tested; untested on Windows Vista, Win 7, FreeBSD
657
-
658
- :Author:
659
- - 2010 written by Jeremy Gray
660
- """
661
- if not (os.path.exists(filename) and
662
- os.path.isdir(os.path.join(os.path.dirname(filename), '.svn'))):
663
- return None, None, None
664
- svnRev, svnLastChangedRev, svnUrl = None, None, None
665
- if (sys.platform in ('darwin', 'freebsd') or
666
- sys.platform.startswith('linux')):
667
- try:
668
- # expects a filename, not dir
669
- svninfo = shellCall(['svn', 'info', filename])
670
- except Exception:
671
- svninfo = ''
672
- for line in svninfo.splitlines():
673
- if line.startswith('URL:'):
674
- svnUrl = line.split()[1]
675
- elif line.startswith('Revision: '):
676
- svnRev = line.split()[1]
677
- elif line.startswith('Last Changed Rev'):
678
- svnLastChangedRev = line.split()[3]
679
- else:
680
- # worked for me on Win XP sp2 with TortoiseSVN (SubWCRev.exe)
681
- try:
682
- stdout = shellCall(['subwcrev', filename])
683
- except Exception:
684
- stdout = ''
685
- for line in stdout.splitlines():
686
- if line.startswith('Last committed at revision'):
687
- svnRev = line.split()[4]
688
- elif line.startswith('Updated to revision'):
689
- svnLastChangedRev = line.split()[3]
690
- return svnRev, svnLastChangedRev, svnUrl
691
-
692
-
693
- def _getHgVersion(filename):
694
- """Tries to discover the mercurial (hg) parent and id of a file.
695
-
696
- Not thoroughly tested; untested on Windows Vista, Win 7, FreeBSD
697
-
698
- :Author:
699
- - 2010 written by Jeremy Gray
700
- """
701
- dirname = os.path.dirname
702
- if (not os.path.exists(filename) or
703
- not os.path.isdir(os.path.join(dirname(filename), '.hg'))):
704
- return None
705
- try:
706
- hgParentLines, err = shellCall(['hg', 'parents', filename],
707
- stderr=True)
708
- changeset = hgParentLines.splitlines()[0].split()[-1]
709
- except Exception:
710
- changeset = ''
711
- try:
712
- hgID, err = shellCall(['hg', 'id', '-nibt', dirname(filename)],
713
- stderr=True)
714
- except Exception:
715
- if err:
716
- hgID = ''
717
-
718
- if len(hgID) or len(changeset):
719
- return hgID.strip() + ' | parent: ' + changeset.strip()
720
- else:
721
- return None
722
-
723
-
724
653
  def _getUserNameUID():
725
654
  """Return user name, UID.
726
655
 
Binary file
@@ -26,7 +26,7 @@ try:
26
26
  _DATA_STORE_AVAILABLE = True
27
27
  except ImportError:
28
28
  print2err('WARNING: pytables package not found. ',
29
- 'ioHub functionality will be disabled.')
29
+ 'ioHub hdf5 datastore functionality will be disabled.')
30
30
  except Exception:
31
31
  printExceptionDetailsToStdErr()
32
32
 
@@ -249,7 +249,7 @@ class ioHubConnection():
249
249
  mouse=hub.devices.mouse
250
250
  mouse_position = mouse.getPosition()
251
251
 
252
- print 'mouse position: ', mouse_position
252
+ print('mouse position: ', mouse_position)
253
253
 
254
254
  # Returns something like:
255
255
  # >> mouse position: [-211.0, 371.0]
@@ -264,9 +264,10 @@ class ioHubConnection():
264
264
  ioHubConfig)
265
265
 
266
266
  if ioHubConnection.ACTIVE_CONNECTION is not None:
267
- raise RuntimeError('An existing ioHubConnection is already open.'
268
- ' Use ioHubConnection.getActiveConnection() '
269
- 'to access it; or use ioHubConnection.quit() '
267
+ raise RuntimeError('An existing ioHubConnection is already open. Use '
268
+ 'iohub.client.ioHubConnection.getActiveConnection() '
269
+ 'to access it; or use '
270
+ 'iohub.ioHubConnection.getActiveConnection().quit() '
270
271
  'to close it.')
271
272
  Computer.psychopy_process = psutil.Process()
272
273
 
@@ -417,13 +418,19 @@ class ioHubConnection():
417
418
  None
418
419
 
419
420
  """
420
- if device_label.lower() == 'all':
421
- self.allEvents = []
422
- self._sendToHubServer(('RPC', 'clearEventBuffer', [True, ]))
423
- try:
424
- self.getDevice('keyboard')._clearLocalEvents()
425
- except:
426
- pass
421
+ if device_label and isinstance(device_label, str):
422
+ device_label = device_label.lower()
423
+ if device_label == 'all':
424
+ self.allEvents = []
425
+ self._sendToHubServer(('RPC', 'clearEventBuffer', [True, ]))
426
+ try:
427
+ self.getDevice('keyboard')._clearLocalEvents()
428
+ except:
429
+ pass
430
+ else:
431
+ d = self.devices.getDevice(device_label)
432
+ if d:
433
+ d.clearEvents()
427
434
  elif device_label in [None, '', False]:
428
435
  self.allEvents = []
429
436
  self._sendToHubServer(('RPC', 'clearEventBuffer', [False, ]))
@@ -432,9 +439,8 @@ class ioHubConnection():
432
439
  except:
433
440
  pass
434
441
  else:
435
- d = self.devices.getDevice(device_label)
436
- if d:
437
- d.clearEvents()
442
+ raise ValueError(
443
+ 'Invalid device_label value: {}'.format(device_label))
438
444
 
439
445
  def sendMessageEvent(self, text, category='', offset=0.0, sec_time=None):
440
446
  """
@@ -1085,8 +1091,12 @@ class ioHubConnection():
1085
1091
  dev_cls_name = dev_cls_name[cls_name_start + 1:]
1086
1092
  else:
1087
1093
  dev_mod_pth = '{0}{1}'.format(dev_mod_pth, dev_name)
1088
-
1089
- dev_import_result = import_device(dev_mod_pth, dev_cls_name)
1094
+ # try to import EyeTracker class from given path
1095
+ try:
1096
+ dev_import_result = import_device(dev_mod_pth, dev_cls_name)
1097
+ except ModuleNotFoundError:
1098
+ # if not found, try importing from root (may have entry point)
1099
+ dev_import_result = import_device("psychopy.iohub.devices", dev_cls_name)
1090
1100
  dev_cls, dev_cls_name, evt_cls_list = dev_import_result
1091
1101
 
1092
1102
  DeviceConstants.addClassMapping(dev_cls)
@@ -1114,9 +1124,7 @@ class ioHubConnection():
1114
1124
  if local_class:
1115
1125
  d = local_class(self, dev_cls_name, dev_config)
1116
1126
  else:
1117
- full_device_class_name = getFullClassName(dev_cls)[len('psychopy.iohub.devices.'):]
1118
- full_device_class_name = full_device_class_name.replace('eyetracker.EyeTracker', 'EyeTracker')
1119
- d = ioHubDeviceView(self, full_device_class_name, dev_cls_name, dev_config)
1127
+ d = ioHubDeviceView(self, dev_mod_pth + "." + dev_cls_name, dev_cls_name, dev_config)
1120
1128
 
1121
1129
  self.devices.addDevice(name, d)
1122
1130
  return d
@@ -1311,7 +1319,9 @@ class ioHubConnection():
1311
1319
  self.udp_client.sendTo(('STOP_IOHUB_SERVER',))
1312
1320
  self.udp_client.close()
1313
1321
  if Computer.iohub_process:
1314
- r = Computer.iohub_process.wait(timeout=5)
1322
+ # This wait() used to have timeout=5, removing it to allow
1323
+ # sufficient time for all iohub devices to be closed.
1324
+ r = Computer.iohub_process.wait()
1315
1325
  print('ioHub Server Process Completed With Code: ', r)
1316
1326
  except TimeoutError:
1317
1327
  print('Warning: TimeoutExpired, Killing ioHub Server process.')
@@ -11,12 +11,13 @@ from ..util import win32MessagePump
11
11
  from ..devices.keyboard import KeyboardInputEvent
12
12
  from ..constants import EventConstants, KeyboardConstants
13
13
 
14
- #pylint: disable=protected-access
14
+ # pylint: disable=protected-access
15
15
 
16
16
  getTime = Computer.getTime
17
17
  kb_cls_attr_names = KeyboardInputEvent.CLASS_ATTRIBUTE_NAMES
18
18
  kb_mod_codes2labels = KeyboardConstants._modifierCodes2Labels
19
19
 
20
+
20
21
  class KeyboardEvent(ioEvent):
21
22
  """
22
23
  Base class for KeyboardPress and KeyboardRelease events.
@@ -42,13 +43,13 @@ class KeyboardEvent(ioEvent):
42
43
  def __init__(self, ioe_array):
43
44
  super(KeyboardEvent, self).__init__(ioe_array)
44
45
  for aname, aindex, in list(self._attrib_index.items()):
45
- setattr(self, '_%s'%aname, ioe_array[aindex])
46
+ setattr(self, '_%s' % aname, ioe_array[aindex])
46
47
  self._modifiers = kb_mod_codes2labels(self._modifiers)
47
48
 
48
49
  @property
49
50
  def key(self):
50
51
  return self._key
51
-
52
+
52
53
  @property
53
54
  def char(self):
54
55
  """The unicode value of the keyboard event, if available. This field is
@@ -87,7 +88,7 @@ class KeyboardEvent(ioEvent):
87
88
  def __str__(self):
88
89
  pstr = ioEvent.__str__(self)
89
90
  return '{}, key: {} char: {}, modifiers: {}'.format(pstr, self.key,
90
- self.char, self.modifiers)
91
+ self.char, self.modifiers)
91
92
 
92
93
  def __eq__(self, v):
93
94
  if isinstance(v, KeyboardEvent):
@@ -113,8 +114,8 @@ class KeyboardRelease(KeyboardEvent):
113
114
 
114
115
  def __init__(self, ioe_array):
115
116
  super(KeyboardRelease, self).__init__(ioe_array)
116
- #self._duration = ioe_array[self._attrib_index['duration']]
117
- #self._press_event_id = ioe_array[self._attrib_index['press_event_id']]
117
+ # self._duration = ioe_array[self._attrib_index['duration']]
118
+ # self._press_event_id = ioe_array[self._attrib_index['press_event_id']]
118
119
 
119
120
  @property
120
121
  def duration(self):
@@ -153,47 +154,47 @@ class KeyboardRelease(KeyboardEvent):
153
154
  class Keyboard(ioHubDeviceView):
154
155
  """The Keyboard device provides access to KeyboardPress and KeyboardRelease
155
156
  events as well as the current keyboard state.
156
-
157
+
157
158
  Examples:
158
159
 
159
160
  A. Print all keyboard events received for 5 seconds::
160
-
161
+
161
162
  from psychopy.iohub import launchHubServer
162
163
  from psychopy.core import getTime
163
-
164
+
164
165
  # Start the ioHub process. 'io' can now be used during the
165
166
  # experiment to access iohub devices and read iohub device events.
166
167
  io = launchHubServer()
167
-
168
+
168
169
  keyboard = io.devices.keyboard
169
-
170
+
170
171
  # Check for and print any Keyboard events received for 5 seconds.
171
172
  stime = getTime()
172
173
  while getTime()-stime < 5.0:
173
174
  for e in keyboard.getEvents():
174
175
  print(e)
175
-
176
+
176
177
  # Stop the ioHub Server
177
178
  io.quit()
178
-
179
+
179
180
  B. Wait for a keyboard press event (max of 5 seconds)::
180
-
181
+
181
182
  from psychopy.iohub import launchHubServer
182
183
  from psychopy.core import getTime
183
-
184
+
184
185
  # Start the ioHub process. 'io' can now be used during the
185
186
  # experiment to access iohub devices and read iohub device events.
186
187
  io = launchHubServer()
187
-
188
+
188
189
  keyboard = io.devices.keyboard
189
-
190
+
190
191
  # Wait for a key keypress event ( max wait of 5 seconds )
191
192
  presses = keyboard.waitForPresses(maxWait=5.0)
192
-
193
+
193
194
  print(presses)
194
-
195
+
195
196
  # Stop the ioHub Server
196
- io.quit()
197
+ io.quit()
197
198
  """
198
199
  KEY_PRESS = EventConstants.KEYBOARD_PRESS
199
200
  KEY_RELEASE = EventConstants.KEYBOARD_RELEASE
@@ -228,8 +229,8 @@ class Keyboard(ioHubDeviceView):
228
229
  """
229
230
  kb_state = self.getCurrentDeviceState()
230
231
 
231
- events = {int(k):v for k,v in list(kb_state.get('events').items())}
232
- pressed_keys = {int(k):v for k,v in list(kb_state.get('pressed_keys',{}).items())}
232
+ events = {int(k): v for k, v in list(kb_state.get('events').items())}
233
+ pressed_keys = {int(k): v for k, v in list(kb_state.get('pressed_keys', {}).items())}
233
234
 
234
235
  self._reporting = kb_state.get('reporting_events')
235
236
  self._pressed_keys.clear()
@@ -289,11 +290,10 @@ class Keyboard(ioHubDeviceView):
289
290
  self._reporting = self.enableEventReporting(r)
290
291
  return self._reporting
291
292
 
292
-
293
293
  def clearEvents(self, event_type=None, filter_id=None):
294
294
  self._clearLocalEvents(event_type)
295
295
  return self._clearEventsRPC(event_type=event_type,
296
- filter_id=filter_id)
296
+ filter_id=filter_id)
297
297
 
298
298
  def getKeys(self, keys=None, chars=None, ignoreKeys=None, mods=None, duration=None,
299
299
  etype=None, clear=True):
@@ -7,7 +7,7 @@
7
7
  import os
8
8
  import atexit
9
9
  import numpy as np
10
- from pkg_resources import parse_version
10
+ from packaging.version import Version
11
11
  from ..server import DeviceEvent
12
12
  from ..constants import EventConstants
13
13
  from ..errors import ioHubError, printExceptionDetailsToStdErr, print2err
@@ -15,7 +15,7 @@ from ..errors import ioHubError, printExceptionDetailsToStdErr, print2err
15
15
  import tables
16
16
  from tables import parameters, StringCol, UInt32Col, UInt16Col, NoSuchNodeError
17
17
 
18
- if parse_version(tables.__version__) < parse_version('3'):
18
+ if Version(tables.__version__) < Version('3'):
19
19
  from tables import openFile as open_file
20
20
 
21
21
  create_table = "createTable"
@@ -12,10 +12,10 @@ import numpy
12
12
 
13
13
  from ..errors import print2err
14
14
 
15
- from pkg_resources import parse_version
15
+ from packaging.version import Version
16
16
  import tables
17
17
 
18
- if parse_version(tables.__version__) < parse_version('3'):
18
+ if Version(tables.__version__) < Version('3'):
19
19
  from tables import openFile as open_file
20
20
 
21
21
  walk_groups = "walkGroups"
@@ -1,5 +1,5 @@
1
1
  global_event_buffer: 2048
2
- udp_port: 9034
2
+ udp_port: 9036
3
3
  msgpump_interval: 0.001
4
4
  data_store:
5
5
  enable: False
Binary file
@@ -6,9 +6,13 @@
6
6
  import collections
7
7
  import copy
8
8
  import os
9
+ import importlib
9
10
  from collections import deque
10
11
  from operator import itemgetter
11
12
 
13
+ import sys
14
+ from psychopy.plugins.util import getEntryPoints
15
+
12
16
  import numpy as np
13
17
 
14
18
  from .computer import Computer
@@ -137,7 +141,8 @@ class ioObject(metaclass=ioObjectMetaClass):
137
141
  rpcList.append(d)
138
142
  return rpcList
139
143
 
140
- ########### Base Abstract Device that all other Devices inherit from ##########
144
+
145
+ # ########## Base Abstract Device that all other Devices inherit from ##########
141
146
 
142
147
 
143
148
  class Device(ioObject):
@@ -264,7 +269,7 @@ class Device(ioObject):
264
269
  None
265
270
 
266
271
  Returns:
267
- (dict): The dictionary of the device configuration settings used
272
+ (dict): The dictionary of the device configuration settings used
268
273
  to create the device.
269
274
 
270
275
  """
@@ -278,14 +283,23 @@ class Device(ioObject):
278
283
  contents.
279
284
 
280
285
  Args:
281
- event_type_id (int): If specified, provides the ioHub DeviceEvent ID for which events should be returned for. Events that have occurred but do not match the event ID specified are ignored. Event type ID's can be accessed via the EventConstants class; all available event types are class attributes of EventConstants.
286
+ event_type_id (int): If specified, provides the ioHub DeviceEvent ID for which events
287
+ should be returned for. Events that have occurred but do not match the event ID
288
+ specified are ignored. Event type ID's can be accessed via the EventConstants class;
289
+ all available event types are class attributes of EventConstants.
282
290
 
283
- clearEvents (int): Can be used to indicate if the events being returned should also be removed from the device event buffer. True (the default) indicates to remove events being returned. False results in events being left in the device event buffer.
291
+ clearEvents (int): Can be used to indicate if the events being returned should also be
292
+ removed from the device event buffer. True (the default) indicates to remove events
293
+ being returned. False results in events being left in the device event buffer.
284
294
 
285
- asType (str): Optional kwarg giving the object type to return events as. Valid values are 'namedtuple' (the default), 'dict', 'list', or 'object'.
295
+ asType (str): Optional kwarg giving the object type to return events as. Valid values
296
+ are 'namedtuple' (the default), 'dict', 'list', or 'object'.
286
297
 
287
298
  Returns:
288
- (list): New events that the ioHub has received since the last getEvents() or clearEvents() call to the device. Events are ordered by the ioHub time of each event, older event at index 0. The event object type is determined by the asType parameter passed to the method. By default a namedtuple object is returned for each event.
299
+ (list): New events that the ioHub has received since the last getEvents() or clearEvents()
300
+ call to the device. Events are ordered by the ioHub time of each event, older event at
301
+ index 0. The event object type is determined by the asType parameter passed to the method.
302
+ By default a namedtuple object is returned for each event.
289
303
 
290
304
  """
291
305
  self._iohub_server.processDeviceEvents()
@@ -321,11 +335,13 @@ class Device(ioObject):
321
335
  call_proc_events=False)
322
336
  else:
323
337
  if filter_id:
324
- [currentEvents.extend([fe for fe in l if fe[
325
- DeviceEvent.EVENT_FILTER_ID_INDEX] == filter_id]) for l in list(self._iohub_event_buffer.values())]
338
+ [currentEvents.extend(
339
+ [fe for fe in event if fe[
340
+ DeviceEvent.EVENT_FILTER_ID_INDEX] == filter_id]
341
+ ) for event in list(self._iohub_event_buffer.values())]
326
342
  else:
327
- [currentEvents.extend(l)
328
- for l in list(self._iohub_event_buffer.values())]
343
+ [currentEvents.extend(event)
344
+ for event in list(self._iohub_event_buffer.values())]
329
345
 
330
346
  if clearEvents is True and len(currentEvents) > 0:
331
347
  self.clearEvents(filter_id=filter_id, call_proc_events=False)
@@ -384,7 +400,10 @@ class Device(ioObject):
384
400
 
385
401
 
386
402
  Args:
387
- enabled (bool): True (default) == Start to report device events to the ioHub Process. False == Stop Reporting Events to the ioHub Process. Most Device types automatically start sending events to the ioHUb Process, however some devices like the EyeTracker and AnlogInput device's do not. The setting to control this behavior is 'auto_report_events'
403
+ enabled (bool): True (default) == Start to report device events to the ioHub Process.
404
+ False == Stop Reporting Events to the ioHub Process. Most Device types automatically
405
+ start sending events to the ioHUb Process, however some devices like the EyeTracker and
406
+ AnlogInput device's do not. The setting to control this behavior is 'auto_report_events'
388
407
 
389
408
  Returns:
390
409
  bool: The current reporting state.
@@ -516,14 +535,14 @@ class Device(ioObject):
516
535
  if self.isReportingEvents():
517
536
  self._native_event_buffer.append(e)
518
537
 
519
- def _addEventListener(self, l, eventTypeIDs):
538
+ def _addEventListener(self, event, eventTypeIDs):
520
539
  for ei in eventTypeIDs:
521
- self._event_listeners.setdefault(ei, []).append(l)
540
+ self._event_listeners.setdefault(ei, []).append(event)
522
541
 
523
- def _removeEventListener(self, l):
542
+ def _removeEventListener(self, event):
524
543
  for etypelisteners in list(self._event_listeners.values()):
525
- if l in etypelisteners:
526
- etypelisteners.remove(l)
544
+ if event in etypelisteners:
545
+ etypelisteners.remove(event)
527
546
 
528
547
  def _getEventListeners(self, forEventType):
529
548
  return self._event_listeners.get(forEventType, [])
@@ -652,10 +671,16 @@ class Device(ioObject):
652
671
  Since any callbacks should take as little time to process as possible,
653
672
  a two stage approach is used to turn a native device event into an ioHub
654
673
  Device event representation:
655
- #. This method is called by the native device interface as a callback, providing the necessary information to be able to create an ioHub event. As little processing should be done in this method as possible.
656
- #. The data passed to this method, along with the time the callback was called, are passed as a tuple to the Device classes _addNativeEventToBuffer method.
657
- #. During the ioHub Servers event processing routine, any new native events that have been added to the ioHub Server using the _addNativeEventToBuffer method are passed individually to the _getIOHubEventObject method, which must also be implemented by the given Device subclass.
658
- #. The _getIOHubEventObject method is responsible for the actual conversion of the native event representation to the required ioHub Event representation for the accociated event type.
674
+ #. This method is called by the native device interface as a callback, providing the necessary
675
+ # information to be able to create an ioHub event. As little processing should be done in this
676
+ # method as possible.
677
+ #. The data passed to this method, along with the time the callback was called, are passed as a
678
+ # tuple to the Device classes _addNativeEventToBuffer method.
679
+ #. During the ioHub Servers event processing routine, any new native events that have been added
680
+ # to the ioHub Server using the _addNativeEventToBuffer method are passed individually to the
681
+ # _getIOHubEventObject method, which must also be implemented by the given Device subclass.
682
+ #. The _getIOHubEventObject method is responsible for the actual conversion of the native event
683
+ # representation to the required ioHub Event representation for the accociated event type.
659
684
 
660
685
  Args:
661
686
  args(tuple): tuple of non keyword arguments passed to the callback.
@@ -703,7 +728,8 @@ class Device(ioObject):
703
728
  def __del__(self):
704
729
  self._close()
705
730
 
706
- ########### Base Device Event that all other Device Events inherit from ##
731
+
732
+ # ########## Base Device Event that all other Device Events inherit from ##
707
733
 
708
734
 
709
735
  class DeviceEvent(ioObject):
@@ -920,16 +946,77 @@ class DeviceEvent(ioObject):
920
946
  @classmethod
921
947
  def createEventAsNamedTuple(cls, valueList):
922
948
  return cls.namedTupleClass(*valueList)
949
+
950
+
923
951
  #
924
952
  # Import Devices and DeviceEvents
925
953
  #
926
954
 
927
955
 
928
- import sys
956
+ def importDeviceModule(modulePath):
957
+ """
958
+ Resolve an import string to import the module for a particular device.
959
+
960
+ Will iteratively check plugin entry points too.
961
+
962
+ Parameters
963
+ ----------
964
+ modulePath : str
965
+ Import path for the requested module
966
+
967
+ Return
968
+ ------
969
+ types.ModuleType
970
+ Requested module
971
+
972
+ Raises
973
+ ------
974
+ ModuleNotFoundError
975
+ If module doesn't exist, will raise this error.
976
+ """
977
+ module = None
978
+ try:
979
+ # try importing as is (this was the only way prior to plugins)
980
+ module = importlib.import_module(modulePath)
981
+ except ModuleNotFoundError:
982
+ # get entry point groups targeting iohub.devices
983
+ entryPoints = getEntryPoints("psychopy.iohub.devices", submodules=True, flatten=False)
984
+ # iterate through found groups
985
+ for group in entryPoints:
986
+ # skip irrelevant groups
987
+ if not modulePath.startswith(group):
988
+ continue
989
+ # get the module of the entry point group
990
+ module_group = importlib.import_module(group)
991
+ # get the entry point target module(s)
992
+ for ep in entryPoints[group]:
993
+ module_name = ep.name
994
+ ep_target = ep.load()
995
+ # bind each entry point module to the existing module tree
996
+ setattr(module_group, module_name, ep_target)
997
+ sys.modules[group + '.' + module_name] = ep_target
998
+
999
+ # re-try importing the module
1000
+ try:
1001
+ module = importlib.import_module(modulePath)
1002
+ except ModuleNotFoundError:
1003
+ pass
1004
+
1005
+ # raise error if all import options failed
1006
+ if module is None:
1007
+ raise ModuleNotFoundError(
1008
+ f"Could not find module `{modulePath}`. Tried importing directly "
1009
+ f"and iteratively using entry points."
1010
+ )
1011
+
1012
+ return module
929
1013
 
930
1014
 
931
1015
  def import_device(module_path, device_class_name):
932
- module = __import__(module_path, fromlist=["{}".format(device_class_name)])
1016
+ # get module from module_path
1017
+ module = importDeviceModule(module_path)
1018
+
1019
+ # get device class from module
933
1020
  device_class = getattr(module, device_class_name)
934
1021
 
935
1022
  setattr(sys.modules[__name__], device_class_name, device_class)
@@ -940,8 +1027,7 @@ def import_device(module_path, device_class_name):
940
1027
  event_constant_string = convertCamelToSnake(
941
1028
  event_class_name[:-5], False)
942
1029
 
943
- event_module = __import__(module_path, fromlist=[event_class_name])
944
- event_class = getattr(event_module, event_class_name)
1030
+ event_class = getattr(module, event_class_name)
945
1031
 
946
1032
  event_class.DEVICE_PARENT = device_class
947
1033
 
@@ -951,6 +1037,7 @@ def import_device(module_path, device_class_name):
951
1037
 
952
1038
  return device_class, device_class_name, event_classes
953
1039
 
1040
+
954
1041
  try:
955
1042
  if getattr(sys.modules[__name__], 'Display', None) is None:
956
1043
  display_class, device_class_name, event_classes = import_device('psychopy.iohub.devices.display', 'Display')