mpf 0.58.0.dev3__py3-none-any.whl → 0.58.0.dev4__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.
Files changed (125) hide show
  1. mpf/_version.py +1 -1
  2. mpf/assets/show.py +3 -3
  3. mpf/commands/__init__.py +3 -2
  4. mpf/commands/game.py +51 -30
  5. mpf/config_players/hardware_sound_player.py +3 -3
  6. mpf/config_players/segment_display_player.py +4 -2
  7. mpf/config_spec.yaml +24 -12
  8. mpf/core/async_mode.py +3 -3
  9. mpf/core/ball_search.py +3 -3
  10. mpf/core/bcp/bcp.py +3 -3
  11. mpf/core/bcp/bcp_transport.py +3 -3
  12. mpf/core/config_player.py +3 -3
  13. mpf/core/config_spec_loader.py +6 -4
  14. mpf/core/config_validator.py +3 -4
  15. mpf/core/data_manager.py +14 -12
  16. mpf/core/delays.py +3 -3
  17. mpf/core/device.py +3 -3
  18. mpf/core/device_manager.py +3 -3
  19. mpf/core/enable_disable_mixin.py +3 -3
  20. mpf/core/events.py +3 -3
  21. mpf/core/file_interface.py +4 -4
  22. mpf/core/file_manager.py +16 -12
  23. mpf/core/logging.py +3 -4
  24. mpf/core/machine.py +100 -20
  25. mpf/core/machine_vars.py +3 -3
  26. mpf/core/mode.py +3 -2
  27. mpf/core/mpf_controller.py +4 -2
  28. mpf/core/placeholder_manager.py +3 -3
  29. mpf/core/platform.py +53 -3
  30. mpf/core/platform_controller.py +3 -3
  31. mpf/core/segment_mappings.py +3 -3
  32. mpf/core/text_ui.py +4 -2
  33. mpf/devices/achievement_group.py +3 -3
  34. mpf/devices/autofire.py +4 -2
  35. mpf/devices/ball_device/ball_device_ejector.py +4 -2
  36. mpf/devices/ball_device/outgoing_balls_handler.py +5 -4
  37. mpf/devices/ball_device/physical_ball_counter.py +3 -3
  38. mpf/devices/ball_routing.py +3 -3
  39. mpf/devices/ball_save.py +3 -3
  40. mpf/devices/blinkenlight.py +3 -2
  41. mpf/devices/combo_switch.py +11 -0
  42. mpf/devices/dc_motor.py +90 -0
  43. mpf/devices/digital_output.py +3 -3
  44. mpf/devices/driver.py +12 -10
  45. mpf/devices/drop_target.py +3 -4
  46. mpf/devices/light.py +25 -11
  47. mpf/devices/multiball_lock.py +3 -3
  48. mpf/devices/segment_display/segment_display.py +3 -3
  49. mpf/devices/shaker.py +12 -9
  50. mpf/devices/shot.py +20 -12
  51. mpf/devices/show_queue.py +3 -2
  52. mpf/devices/spinner.py +2 -2
  53. mpf/devices/switch.py +8 -5
  54. mpf/devices/timer.py +3 -3
  55. mpf/file_interfaces/pickle_interface.py +1 -1
  56. mpf/file_interfaces/yaml_interface.py +23 -7
  57. mpf/mpfconfig.yaml +2 -1
  58. mpf/platforms/base_serial_communicator.py +2 -3
  59. mpf/platforms/fadecandy.py +3 -3
  60. mpf/platforms/fast/communicators/base.py +92 -14
  61. mpf/platforms/fast/communicators/exp.py +5 -3
  62. mpf/platforms/fast/communicators/net_neuron.py +30 -1
  63. mpf/platforms/fast/communicators/seg.py +3 -2
  64. mpf/platforms/fast/fast.py +56 -12
  65. mpf/platforms/fast/fast_defines.py +5 -0
  66. mpf/platforms/fast/fast_driver.py +3 -2
  67. mpf/platforms/fast/fast_exp_board.py +1 -1
  68. mpf/platforms/fast/fast_motor.py +59 -0
  69. mpf/platforms/fast/fast_shaker.py +1 -2
  70. mpf/platforms/fast/fast_switch.py +0 -2
  71. mpf/platforms/interfaces/motor_platform_interface.py +34 -0
  72. mpf/platforms/lisy/lisy.py +1 -1
  73. mpf/platforms/mypinballs/mypinballs.py +1 -1
  74. mpf/platforms/openpixel.py +3 -3
  75. mpf/platforms/opp/opp.py +3 -2
  76. mpf/platforms/opp/opp_coil.py +3 -3
  77. mpf/platforms/opp/opp_serial_communicator.py +3 -2
  78. mpf/platforms/opp/opp_servo.py +3 -2
  79. mpf/platforms/p3_roc.py +3 -3
  80. mpf/platforms/p_roc.py +3 -3
  81. mpf/platforms/p_roc_common.py +2 -2
  82. mpf/platforms/p_roc_devices.py +3 -3
  83. mpf/platforms/pkone/pkone_coil.py +3 -3
  84. mpf/platforms/pkone/pkone_lights.py +4 -2
  85. mpf/platforms/pkone/pkone_serial_communicator.py +4 -2
  86. mpf/platforms/pkone/pkone_servo.py +3 -2
  87. mpf/platforms/pkone/pkone_switch.py +3 -2
  88. mpf/platforms/pololu_maestro.py +1 -1
  89. mpf/platforms/smart_virtual.py +8 -3
  90. mpf/platforms/spike/spike.py +3 -3
  91. mpf/platforms/system11.py +3 -3
  92. mpf/platforms/virtual.py +40 -2
  93. mpf/plugins/auditor.py +3 -2
  94. mpf/plugins/virtual_segment_display_connector.py +3 -2
  95. mpf/tests/machine_files/auditor/config/config.yaml +2 -0
  96. mpf/tests/machine_files/data_manager/config/config.yaml +4 -1
  97. mpf/tests/machine_files/dc_motor/config/config.yaml +18 -0
  98. mpf/tests/machine_files/shots/config/test_shots.yaml +2 -0
  99. mpf/tests/machine_files/shots/modes/base2/config/base2.yaml +4 -0
  100. mpf/tests/machine_files/soft_power_down/config/config.yaml +1 -0
  101. mpf/tests/platforms/fast.py +3 -3
  102. mpf/tests/test_BcpInterface.py +6 -6
  103. mpf/tests/test_ComboSwitches.py +78 -0
  104. mpf/tests/test_DCMotor.py +37 -0
  105. mpf/tests/test_Fast_Communicator.py +183 -0
  106. mpf/tests/test_Fast_Exp.py +13 -0
  107. mpf/tests/test_Fast_Power.py +82 -0
  108. mpf/tests/test_FileManager.py +75 -0
  109. mpf/tests/test_ServiceCli.py +1 -1
  110. mpf/tests/test_Shots.py +14 -0
  111. mpf/tests/test_SoftPowerDown.py +69 -0
  112. mpf/tests/test_VirtualPinball.py +2 -2
  113. mpf/tests/test_YamlInterface.py +33 -0
  114. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/METADATA +2 -4
  115. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/RECORD +120 -115
  116. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/WHEEL +1 -1
  117. mpf/plugins/twitch/__init__.py +0 -1
  118. mpf/plugins/twitch/twitch_client.py +0 -254
  119. mpf/plugins/twitch_bot.py +0 -57
  120. mpf/tests/machine_files/twitch_client/config/config.yaml +0 -6
  121. mpf/tests/test_TwitchClient.py +0 -175
  122. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/entry_points.txt +0 -0
  123. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/licenses/AUTHORS.md +0 -0
  124. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/licenses/LICENSE.md +0 -0
  125. {mpf-0.58.0.dev3.dist-info → mpf-0.58.0.dev4.dist-info}/top_level.txt +0 -0
mpf/_version.py CHANGED
@@ -10,7 +10,7 @@ PyPI.
10
10
 
11
11
  """
12
12
 
13
- __version__ = '0.58.0.dev3' # Also consider whether MPF-MC pyproject.toml should be updated
13
+ __version__ = '0.58.0.dev4' # Also consider whether MPF-MC pyproject.toml should be updated
14
14
  '''The full version of MPF.'''
15
15
 
16
16
  __short_version__ = '0.58'
mpf/assets/show.py CHANGED
@@ -2,15 +2,15 @@
2
2
  import re
3
3
  from collections import namedtuple
4
4
 
5
- from typing import List, Dict, Any, Optional
5
+ from typing import List, Dict, Any, Optional, TYPE_CHECKING
6
6
 
7
7
  from mpf.core.assets import AssetPool
8
8
  from mpf.core.config_validator import RuntimeToken
9
9
  from mpf.core.utility_functions import Util
10
10
  from mpf.exceptions.config_file_error import ConfigFileError
11
11
 
12
- MYPY = False
13
- if MYPY: # pragma: no cover
12
+
13
+ if TYPE_CHECKING:
14
14
  from typing import NoReturn # pylint: disable-msg=cyclic-import,unused-import
15
15
 
16
16
  __api__ = ['Show', 'RunningShow', 'ShowPool']
mpf/commands/__init__.py CHANGED
@@ -1,9 +1,9 @@
1
1
  """MPF CLI commands."""
2
2
  import argparse
3
3
  from importlib import import_module
4
+ import importlib.metadata
4
5
  import os
5
6
  import sys
6
- from pkg_resources import iter_entry_points
7
7
 
8
8
 
9
9
  import mpf.core
@@ -137,7 +137,8 @@ class CommandLineUtility(MpfCommandLineParser):
137
137
 
138
138
  This is used from mpf mc.
139
139
  """
140
- for entry_point in iter_entry_points(group='mpf.command', name=None):
140
+ eps = importlib.metadata.entry_points(group='mpf.command')
141
+ for entry_point in eps:
141
142
  command, function_ref = entry_point.load()()
142
143
  self.external_commands[command] = function_ref
143
144
 
mpf/commands/game.py CHANGED
@@ -31,13 +31,11 @@ class Command:
31
31
  self.machine = None
32
32
  self._sigint_count = 0
33
33
 
34
- parser = argparse.ArgumentParser(
35
- description='Starts the MPF game engine')
34
+ parser = argparse.ArgumentParser(description='Starts the MPF game engine')
36
35
 
37
36
  parser.add_argument("-a",
38
37
  action="store_true", dest="no_load_cache",
39
- help="Forces the config to be loaded from files "
40
- "and not cache")
38
+ help="Forces the config to be loaded from files and not cache")
41
39
 
42
40
  parser.add_argument("-A",
43
41
  action="store_false", dest="create_config_cache",
@@ -45,24 +43,24 @@ class Command:
45
43
 
46
44
  parser.add_argument("-b",
47
45
  action="store_false", dest="bcp", default=True,
48
- help="Runs MPF without making a connection "
49
- "attempt to a BCP Server")
46
+ help="Runs MPF without making a connection attempt to a BCP Server")
50
47
 
51
48
  parser.add_argument("-c",
52
49
  action="store", dest="configfile",
53
50
  default="config.yaml", metavar='config_file',
54
- help="The name of a config file to load. Default "
55
- "is "
56
- "config.yaml. Multiple files can be used "
57
- "via a comma-"
51
+ help="The name of a config file to load. Default is "
52
+ "config.yaml. Multiple files can be used via a comma-"
58
53
  "separated list (no spaces between)")
59
54
 
55
+ parser.add_argument("-e",
56
+ action="store_false", dest="echo_args", default=True,
57
+ help="Stop logging the raw command line arguments on startup.")
58
+
60
59
  parser.add_argument("-f",
61
60
  action="store_true", dest="force_assets_load",
62
61
  default=False,
63
62
  help="Load all assets upon startup. Useful for "
64
- "ensuring all assets are set up properly "
65
- "during development.")
63
+ "ensuring all assets are set up properly during development.")
66
64
 
67
65
  parser.add_argument("-pit", action="store", dest="platform_integration_test",
68
66
  metavar='pit_file', default=False,
@@ -91,8 +89,8 @@ class Command:
91
89
 
92
90
  parser.add_argument("-P",
93
91
  action="store_true", dest="production", default=False,
94
- help="Production mode. Will suppress errors, wait for hardware on start and "
95
- "try to exit when startup fails. Run this inside a loop.")
92
+ help="Production mode. Will suppress errors, wait for hardware on start "
93
+ "and try to exit when startup fails. Run this inside a loop.")
96
94
 
97
95
  parser.add_argument("-t",
98
96
  action="store_false", dest='text_ui', default=True,
@@ -102,43 +100,35 @@ class Command:
102
100
  action="store_const", dest="loglevel",
103
101
  const=logging.DEBUG,
104
102
  default=15,
105
- help="Enables verbose logging to the"
106
- " log file")
103
+ help="Enables verbose logging to the log file")
107
104
 
108
105
  parser.add_argument("-V",
109
106
  action="store_const", dest="consoleloglevel",
110
107
  const=logging.DEBUG,
111
108
  default=logging.INFO,
112
109
  help="Enables verbose logging to the console. DO "
113
- "NOTE: you must also use -v for "
114
- "this to work.")
110
+ "NOTE: you must also use -v for this to work.")
115
111
 
116
112
  parser.add_argument("-x",
117
113
  action="store_const", dest="force_platform",
118
114
  const='virtual',
119
- help="Forces the virtual platform to be "
120
- "used for all devices")
115
+ help="Forces the virtual platform to be used for all devices")
121
116
 
122
117
  parser.add_argument("--vpx",
123
118
  action="store_const", dest="force_platform",
124
119
  const='virtual_pinball',
125
- help="Forces the virtual_pinball platform to be "
126
- "used for all devices")
120
+ help="Forces the virtual_pinball platform to be used for all devices")
127
121
 
128
122
  parser.add_argument("--syslog_address",
129
123
  action="store", dest="syslog_address",
130
- help="Log to the specified syslog address. This "
131
- "can be a domain socket such as /dev/og on "
132
- "Linux or /var/run/syslog on Mac. "
133
- "Alternatively, you an specify host:port for "
134
- "remote logging over UDP.")
124
+ help="Log to the specified syslog address. This can be a domain socket "
125
+ "such as /dev/og on Linux or /var/run/syslog on Mac. Alternatively, "
126
+ "you an specify host:port for remote logging over UDP.")
135
127
 
136
128
  parser.add_argument("-X",
137
129
  action="store_const", dest="force_platform",
138
130
  const='smart_virtual',
139
- help="Forces the smart virtual platform to be "
140
- "used for all"
141
- " devices")
131
+ help="Forces the smart virtual platform to be used for all devices")
142
132
 
143
133
  # The following are just included for full compatibility with mc
144
134
  # which is needed when using "mpf both".
@@ -180,6 +170,8 @@ class Command:
180
170
  console_log.setFormatter(logging.Formatter(
181
171
  '%(asctime)s.%(msecs)03d : %(levelname)s [%(name)s] %(message)s', "%H:%M:%S"))
182
172
 
173
+ console_log.addFilter(ConsoleCustomLevelFilter())
174
+
183
175
  # initialize async handler for console
184
176
  console_log_queue = Queue()
185
177
  console_queue_handler = QueueHandler(console_log_queue)
@@ -194,6 +186,7 @@ class Command:
194
186
  else:
195
187
  formatter = logging.Formatter('%(asctime)s : %(levelname)s : %(name)s : %(message)s')
196
188
  file_log.setFormatter(formatter)
189
+ file_log.addFilter(FileCustomLevelFilter())
197
190
 
198
191
  # initialize async handler for file log
199
192
  file_log_queue = Queue()
@@ -221,6 +214,10 @@ class Command:
221
214
 
222
215
  signal.signal(signal.SIGINT, self.sigint_handler)
223
216
 
217
+ if self.args.echo_args:
218
+ raw_cmd = " ".join(args) if args else "[None]"
219
+ logger.info("Raw command line: %s", raw_cmd)
220
+
224
221
  if not self.args.production:
225
222
  config_loader = YamlMultifileConfigLoader(machine_path, self.args.configfile,
226
223
  not self.args.no_load_cache, self.args.create_config_cache)
@@ -248,6 +245,8 @@ class Command:
248
245
  def sigint_handler(self, signum=None, frame=None):
249
246
  """Handle SIGINT."""
250
247
  del signum, frame
248
+ if self.machine:
249
+ self.machine.error_log("SIGINT received")
251
250
  self._sigint_count += 1
252
251
  if self._sigint_count > 1:
253
252
  self.exit("Received second SIGINT. Will exit ungracefully!")
@@ -280,3 +279,25 @@ class Command:
280
279
  input('Press ENTER to continue...') # nosec
281
280
 
282
281
  sys.exit()
282
+
283
+
284
+ class ConsoleCustomLevelFilter(logging.Filter):
285
+
286
+ """Filters out custom levels meant strictly for files."""
287
+
288
+ def filter(self, record):
289
+ """Filters: 11 = DEBUG (File Only), 21 = INFO (File Only)."""
290
+ if record.levelno in (11, 21):
291
+ return False
292
+ return True
293
+
294
+
295
+ class FileCustomLevelFilter(logging.Filter):
296
+
297
+ """Filters out custom levels meant strictly for console."""
298
+
299
+ def filter(self, record):
300
+ """Filters: 12 = DEBUG (Console Only), 22 = INFO (Console Only)."""
301
+ if record.levelno in (12, 22):
302
+ return False
303
+ return True
@@ -1,10 +1,10 @@
1
1
  """Config player for sounds on an external sound card."""
2
- from typing import List
2
+ from typing import List, TYPE_CHECKING
3
3
 
4
4
  from mpf.config_players.device_config_player import DeviceConfigPlayer
5
5
 
6
- MYPY = False
7
- if MYPY: # pragma: no cover
6
+
7
+ if TYPE_CHECKING:
8
8
  from mpf.devices.hardware_sound_system import HardwareSoundSystem # pylint: disable-msg=cyclic-import,unused-import; # noqa
9
9
 
10
10
 
@@ -1,12 +1,14 @@
1
1
  """Config player for text on segment displays."""
2
+ from typing import TYPE_CHECKING
3
+
2
4
  from mpf.core.delays import DelayManager
3
5
 
4
6
  from mpf.config_players.device_config_player import DeviceConfigPlayer
5
7
  from mpf.platforms.interfaces.segment_display_platform_interface import FlashingType
6
8
  from mpf.devices.segment_display.transition_manager import TransitionManager
7
9
 
8
- MYPY = False
9
- if MYPY: # pragma: no cover
10
+
11
+ if TYPE_CHECKING:
10
12
  from typing import Dict # pylint: disable-msg=cyclic-import,unused-import
11
13
  from mpf.devices.segment_display.segment_display import \
12
14
  SegmentDisplay # pylint: disable-msg=cyclic-import,unused-import
mpf/config_spec.yaml CHANGED
@@ -98,8 +98,8 @@ auditor:
98
98
  audit: list|str|None
99
99
  autosave: single|bool|true
100
100
  autosave_events: single|bool|true
101
- events: list|event_handler|None
102
- player: list|str|None
101
+ events: set|event_handler|None
102
+ player: set|str|None
103
103
  audit_shots: single|bool|true
104
104
  missing_switch_min_games: single|int|10
105
105
  num_player_top_records: single|int|1
@@ -326,6 +326,18 @@ coils:
326
326
  custom_code:
327
327
  __valid_in__: machine
328
328
  __type__: list
329
+ dc_motors:
330
+ __valid_in__: machine
331
+ __type__: device
332
+ default_power: single|float|0.0
333
+ number: single|str|
334
+ control_events: dict|str:subconfig(dc_motors_pulse_settings)|None
335
+ platform: single|str|None
336
+ platform_settings: single|dict|None
337
+ dc_motors_pulse_settings:
338
+ action: single|enum(pulse,reverse_pulse,stop)|pulse
339
+ power: single|float|None
340
+ duration: single|template_secs|None
329
341
  digital_outputs:
330
342
  __valid_in__: machine
331
343
  __type__: device
@@ -639,7 +651,7 @@ fast_net:
639
651
  console_log: single|enum(none,basic,full)|none
640
652
  file_log: single|enum(none,basic,full)|basic
641
653
  controller: single|enum(neuron,nano,sys11,wpc89,wpc95)|
642
- watchdog: single|ms|1000
654
+ watchdog: single|ms|500
643
655
  default_quick_debounce_open: single|ms|2
644
656
  default_quick_debounce_close: single|ms|2
645
657
  default_normal_debounce_open: single|ms|4
@@ -647,6 +659,8 @@ fast_net:
647
659
  mute_unconfigured_switches: single|bool|false
648
660
  gi_hz: single|int|30
649
661
  lamp_hz: single|int|30
662
+ soft_power_hold_ms: single|ms|2500
663
+ soft_power_powerdown_delay: single|ms|4000
650
664
  fast_io_loop:
651
665
  model: single|str| # FP-I/O-0804, etc.
652
666
  order: single|enum(1,2,3,4,5,6,7,8,9)|
@@ -814,6 +828,7 @@ hardware:
814
828
  i2c: list|str|default
815
829
  stepper_controllers: list|str|default
816
830
  shaker_controllers: list|str|default
831
+ motor_controllers: list|str|default
817
832
  hardware_sound_system: list|str|default
818
833
  hardware_sound_systems:
819
834
  __valid_in__: machine
@@ -1039,6 +1054,7 @@ machine:
1039
1054
  __type__: config
1040
1055
  balls_installed: single|int|1
1041
1056
  min_balls: single|int|1
1057
+ soft_shutdown_exit_command: single|str|None
1042
1058
  machine_vars:
1043
1059
  __valid_in__: machine
1044
1060
  __type__: config_dict
@@ -1129,6 +1145,10 @@ mpf:
1129
1145
  mpf-mc:
1130
1146
  __valid_in__: machine # todo add to validator
1131
1147
  __type__: config
1148
+ data_manager:
1149
+ __valid_in__: machine
1150
+ __type__: config
1151
+ use_fsync: single|bool|None
1132
1152
  multiballs:
1133
1153
  __valid_in__: machine, mode
1134
1154
  __type__: device
@@ -1585,6 +1605,7 @@ shots:
1585
1605
  switches: list|machine(switches)|None
1586
1606
  start_enabled: single|bool|None
1587
1607
  delay_switch: dict|machine(switches):ms|None
1608
+ delay_event_list: dict|event_handler:ms|None
1588
1609
  persist_enable: single|bool|true
1589
1610
  playfield: single|machine(playfields)|playfield
1590
1611
  priority: single|int|0
@@ -2150,15 +2171,6 @@ trinamics_steprocker:
2150
2171
  __valid_in__: machine
2151
2172
  __type__: config
2152
2173
  port: single|str|
2153
- twitch_client:
2154
- __valid_in__: machine
2155
- __type__: config
2156
- user: single|str|None
2157
- password: single|str|None
2158
- channel: single|str|None
2159
- user_var: single|str|None
2160
- password_var: single|str|None
2161
- channel_var: single|str|None
2162
2174
  variable_player:
2163
2175
  __valid_in__: machine, mode
2164
2176
  __type__: config_player
mpf/core/async_mode.py CHANGED
@@ -1,13 +1,13 @@
1
1
  """Base class for asyncio modes."""
2
- from typing import Optional
2
+ from typing import Optional, TYPE_CHECKING
3
3
 
4
4
  import abc
5
5
  import asyncio
6
6
 
7
7
  from mpf.core.mode import Mode
8
8
 
9
- MYPY = False
10
- if MYPY: # pragma: no cover
9
+
10
+ if TYPE_CHECKING:
11
11
  from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
12
12
 
13
13
 
mpf/core/ball_search.py CHANGED
@@ -1,14 +1,14 @@
1
1
  """Implements the ball search procedure."""
2
2
  from collections import namedtuple
3
3
 
4
- from typing import List
4
+ from typing import List, TYPE_CHECKING
5
5
 
6
6
  from mpf.core.delays import DelayManager
7
7
  from mpf.core.machine import MachineController
8
8
  from mpf.core.mpf_controller import MpfController
9
9
 
10
- MYPY = False
11
- if MYPY: # pragma: no cover
10
+
11
+ if TYPE_CHECKING:
12
12
  from mpf.devices.playfield import Playfield # pylint: disable-msg=cyclic-import,unused-import
13
13
 
14
14
  BallSearchCallback = namedtuple("BallSearchCallback", ["priority", "callback", "name", "restore_callback"])
mpf/core/bcp/bcp.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """BCP module."""
2
2
  import asyncio
3
3
  from functools import partial
4
- from typing import List
4
+ from typing import List, TYPE_CHECKING
5
5
 
6
6
  from mpf.core.events import QueuedEvent
7
7
  from mpf.core.mpf_controller import MpfController
@@ -12,8 +12,8 @@ from mpf.core.utility_functions import Util
12
12
  from mpf.core.bcp.bcp_interface import BcpInterface
13
13
  from mpf.core.bcp.bcp_transport import BcpTransportManager
14
14
 
15
- MYPY = False
16
- if MYPY: # pragma: no cover
15
+
16
+ if TYPE_CHECKING:
17
17
  from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
18
18
 
19
19
 
@@ -1,13 +1,13 @@
1
1
  """Classes which manage BCP transports."""
2
2
  from collections import defaultdict
3
3
 
4
- from typing import Union
4
+ from typing import Union, TYPE_CHECKING
5
5
 
6
6
  from mpf.core.bcp.bcp_client import BaseBcpClient
7
7
  from mpf.core.utility_functions import Util
8
8
 
9
- MYPY = False # noqa
10
- if MYPY:
9
+ # noqa
10
+ if TYPE_CHECKING:
11
11
  from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
12
12
 
13
13
 
mpf/core/config_player.py CHANGED
@@ -3,7 +3,7 @@ import abc
3
3
  import asyncio
4
4
  import re
5
5
  from functools import partial
6
- from typing import List
6
+ from typing import List, TYPE_CHECKING
7
7
 
8
8
  from mpf.core.machine import MachineController
9
9
  from mpf.core.mode import Mode
@@ -11,8 +11,8 @@ from mpf.core.logging import LogMixin
11
11
  from mpf.exceptions.config_file_error import ConfigFileError
12
12
  from mpf.core.placeholder_manager import ConditionalEvent
13
13
 
14
- MYPY = False
15
- if MYPY: # pragma: no cover
14
+
15
+ if TYPE_CHECKING:
16
16
  from mpf.core.placeholder_manager import BoolTemplate # pylint: disable-msg=cyclic-import,unused-import
17
17
  from typing import Dict # pylint: disable-msg=cyclic-import,unused-import
18
18
 
@@ -1,11 +1,12 @@
1
1
  """Parse config_spec."""
2
- from pkg_resources import iter_entry_points
2
+ from typing import TYPE_CHECKING
3
+ import importlib.metadata
3
4
 
4
5
  from mpf.core.utility_functions import Util
5
6
  from mpf.file_interfaces.yaml_interface import YamlInterface
6
7
 
7
- MYPY = False
8
- if MYPY: # pragma: no cover
8
+
9
+ if TYPE_CHECKING:
9
10
  from mpf.core.device import Device # pylint: disable-msg=cyclic-import,unused-import
10
11
 
11
12
 
@@ -37,7 +38,8 @@ class ConfigSpecLoader:
37
38
  @staticmethod
38
39
  def load_external_platform_config_specs(config):
39
40
  """Load config spec for external platforms."""
40
- for platform_entry in iter_entry_points(group='mpf.platforms'):
41
+ eps = importlib.metadata.entry_points(group='mpf.platforms')
42
+ for platform_entry in eps:
41
43
  config_spec = platform_entry.load().get_config_spec()
42
44
 
43
45
  if config_spec:
@@ -6,8 +6,7 @@ import re
6
6
  from collections import namedtuple
7
7
  from copy import deepcopy
8
8
 
9
- from typing import Any
10
- from typing import Dict
9
+ from typing import Any, Dict, TYPE_CHECKING
11
10
 
12
11
  from mpf.core.config_spec_loader import ConfigSpecLoader
13
12
  from mpf.core.rgb_color import NAMED_RGB_COLORS, RGBColor
@@ -15,8 +14,8 @@ from mpf.exceptions.config_file_error import ConfigFileError
15
14
  from mpf.file_interfaces.yaml_interface import YamlInterface
16
15
  from mpf.core.utility_functions import Util
17
16
 
18
- MYPY = False
19
- if MYPY: # pragma: no cover
17
+
18
+ if TYPE_CHECKING:
20
19
  from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
21
20
 
22
21
 
mpf/core/data_manager.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import copy
4
4
  import os
5
5
  import errno
6
+ import sys
6
7
  import threading
7
8
  import time
8
9
  import _thread
@@ -17,10 +18,10 @@ class DataManager(MpfController):
17
18
 
18
19
  config_name = "data_manager"
19
20
 
20
- __slots__ = ["name", "min_wait_secs", "filename", "data", "_dirty"]
21
+ __slots__ = ["config", "name", "min_wait_secs", "filename", "data", "_dirty", "use_fsync"]
21
22
 
22
23
  def __init__(self, machine, name, min_wait_secs=1):
23
- """Initialize data manger.
24
+ """Initialize data manager.
24
25
 
25
26
  The DataManager is responsible for reading and writing data to/from a
26
27
  file on disk.
@@ -48,6 +49,14 @@ class DataManager(MpfController):
48
49
  else:
49
50
  raise AssertionError("Invalid path {} for {}".format(config_path, name))
50
51
 
52
+ self.config = self.machine.config_validator.validate_config(
53
+ "data_manager", self.machine.config.get('data_manager'))
54
+
55
+ maybe_use_fsync = self.config.get('use_fsync', None)
56
+ if maybe_use_fsync is None:
57
+ maybe_use_fsync = sys.platform != 'win32'
58
+ self.use_fsync = maybe_use_fsync
59
+
51
60
  self.data = dict()
52
61
  self._dirty = threading.Event()
53
62
 
@@ -116,29 +125,22 @@ class DataManager(MpfController):
116
125
 
117
126
  def _writing_thread(self): # pragma: no cover
118
127
  # prevent early writes at start-up
119
- data = None
120
128
  time.sleep(self.min_wait_secs)
121
129
  while not self.machine.thread_stopper.is_set():
122
130
  if not self._dirty.wait(1):
123
131
  continue
124
- while FileManager.is_busy:
125
- time.sleep(0.2)
126
132
  self._dirty.clear()
127
133
 
128
- data = copy.deepcopy(self.data)
129
134
  # save data
130
135
  try:
131
- FileManager.save(self.filename, data)
136
+ FileManager.save(self.filename, copy.deepcopy(self.data), self.use_fsync)
132
137
  except Exception as e: # pylint: disable=broad-exception-caught
133
138
  # If the file writer has an exception handle it here. Otherwise
134
139
  # this thread will die and all subsequent write attempts will no-op.
135
140
  self.info_log("ERROR writing file %s: %s", self.filename, e)
136
- data = None
137
141
  # prevent too many writes
138
142
  time.sleep(self.min_wait_secs)
139
143
 
140
144
  # if dirty write data one last time during shutdown
141
- if data and self._dirty.is_set():
142
- while FileManager.is_busy:
143
- time.sleep(0.2)
144
- FileManager.save(self.filename, data)
145
+ if self._dirty.is_set():
146
+ FileManager.save(self.filename, copy.deepcopy(self.data), self.use_fsync)
mpf/core/delays.py CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  import uuid
4
4
  from functools import partial
5
- from typing import Any, Callable, Dict, Tuple
5
+ from typing import Any, Callable, Dict, Tuple, TYPE_CHECKING
6
6
  from mpf.core.mpf_controller import MpfController
7
7
 
8
- MYPY = False
9
- if MYPY: # pragma: no cover
8
+
9
+ if TYPE_CHECKING:
10
10
  from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
11
11
 
12
12
  __api__ = ['DelayManager']
mpf/core/device.py CHANGED
@@ -1,13 +1,13 @@
1
1
  """Contains the Device base class."""
2
2
  import abc
3
3
 
4
- from typing import List, Any, Optional
4
+ from typing import List, Any, Optional, TYPE_CHECKING
5
5
 
6
6
  from mpf.core.machine import MachineController
7
7
  from mpf.core.logging import LogMixin
8
8
 
9
- MYPY = False
10
- if MYPY: # pragma: no cover
9
+
10
+ if TYPE_CHECKING:
11
11
  from mpf.core.mode import Mode # pylint: disable-msg=cyclic-import,unused-import
12
12
  from mpf.core.platform import BasePlatform # pylint: disable-msg=cyclic-import,unused-import; # noqa
13
13
 
@@ -1,13 +1,13 @@
1
1
  """Contains the DeviceManager base class."""
2
2
  import asyncio
3
3
 
4
- from typing import Callable, Tuple, List, Generator, Dict
4
+ from typing import Callable, Tuple, List, Generator, Dict, TYPE_CHECKING
5
5
 
6
6
  from mpf.core.utility_functions import Util
7
7
  from mpf.core.mpf_controller import MpfController
8
8
 
9
- MYPY = False
10
- if MYPY: # pragma: no cover
9
+
10
+ if TYPE_CHECKING:
11
11
  from mpf.core.device import Device # pylint: disable-msg=cyclic-import,unused-import
12
12
 
13
13
 
@@ -1,5 +1,5 @@
1
1
  """Implements enable and disable events for devices."""
2
- from typing import Optional
2
+ from typing import Optional, TYPE_CHECKING
3
3
 
4
4
  import abc
5
5
 
@@ -12,8 +12,8 @@ from mpf.core.events import event_handler
12
12
  from mpf.core.mode_device import ModeDevice
13
13
  from mpf.core.machine import MachineController
14
14
 
15
- MYPY = False
16
- if MYPY: # pragma: no cover
15
+
16
+ if TYPE_CHECKING:
17
17
  from mpf.core.mode import Mode # pylint: disable-msg=cyclic-import,unused-import
18
18
  from mpf.core.player import Player # pylint: disable-msg=cyclic-import,unused-import
19
19
 
mpf/core/events.py CHANGED
@@ -7,12 +7,12 @@ import asyncio
7
7
  from functools import partial, lru_cache
8
8
  from unittest.mock import MagicMock
9
9
 
10
- from typing import Dict, Any, Tuple, Optional, Callable, List
10
+ from typing import Dict, Any, Tuple, Optional, Callable, List, TYPE_CHECKING
11
11
 
12
12
  from mpf.core.mpf_controller import MpfController
13
13
 
14
- MYPY = False
15
- if MYPY: # pragma: no cover
14
+
15
+ if TYPE_CHECKING:
16
16
  from mpf.core.machine import MachineController # pylint: disable-msg=cyclic-import,unused-import
17
17
  from mpf.core.placeholder_manager import BaseTemplate # pylint: disable-msg=cyclic-import,unused-import
18
18
  from typing import Deque # pylint: disable-msg=cyclic-import,unused-import