def _run_subprocess_until_exit(self) -> None: if self._subprocess is None: return assert current_thread() is self._subprocess_thread assert self._subprocess.stdin is not None # Send the initial server config which should kick things off. # (but make sure its values are still valid first) dataclass_validate(self._config) self._send_server_command(StartServerModeCommand(self._config)) while True: # If the app is trying to shut down, nope out immediately. if self._done: break # Pass along any commands to our process. with self._subprocess_commands_lock: for incmd in self._subprocess_commands: # If we're passing a raw string to exec, no need to wrap it # in any proper structure. if isinstance(incmd, str): self._subprocess.stdin.write((incmd + '\n').encode()) self._subprocess.stdin.flush() else: self._send_server_command(incmd) self._subprocess_commands = [] # Request restarts/shut-downs for various reasons. self._request_shutdowns_or_restarts() # If they want to force-kill our subprocess, simply exit this # loop; the cleanup code will kill the process if its still # alive. if (self._subprocess_force_kill_time is not None and time.time() > self._subprocess_force_kill_time): print( f'{Clr.CYN}Immediate shutdown time limit' f' ({self.IMMEDIATE_SHUTDOWN_TIME_LIMIT:.1f} seconds)' f' expired; force-killing subprocess...{Clr.RST}', flush=True) break # Watch for the server process exiting.. code: Optional[int] = self._subprocess.poll() if code is not None: clr = Clr.CYN if code == 0 else Clr.RED print( f'{clr}Server subprocess exited' f' with code {code}.{Clr.RST}', flush=True) self._subprocess_exited_cleanly = (code == 0) break time.sleep(0.25)
def _run_process_until_exit(self) -> None: assert self._process is not None assert self._process.stdin is not None # Send the initial server config which should kick things off. # (but make sure its values are still valid first) dataclass_validate(self._config) self._send_server_command(StartServerModeCommand(self._config)) while True: # If the app is trying to shut down, nope out immediately. if self._done: break # Pass along any commands to our process. with self._process_commands_lock: for incmd in self._process_commands: # If we're passing a raw string to exec, no need to wrap it # in any proper structure. if isinstance(incmd, str): self._process.stdin.write((incmd + '\n').encode()) self._process.stdin.flush() else: self._send_server_command(incmd) self._process_commands = [] # Request a soft restart after a while. assert self._process_launch_time is not None sincelaunch = time.time() - self._process_launch_time if (self._restart_minutes is not None and sincelaunch > (self._restart_minutes * 60.0) and not self._process_sent_auto_restart): print(f'{Clr.CYN}restart_minutes ({self._restart_minutes})' f' elapsed; requesting child-process' f' soft restart...{Clr.RST}') self.restart() self._process_sent_auto_restart = True # Watch for the process exiting. code: Optional[int] = self._process.poll() if code is not None: if code == 0: clr = Clr.CYN slp = 0.0 else: clr = Clr.SRED slp = 5.0 # Avoid super fast death loops. print(f'{clr}Server child-process exited' f' with code {code}.{Clr.RST}') self._reset_process_vars() time.sleep(slp) break time.sleep(0.25)
def test_validate() -> None: """Testing validation.""" @dataclass class _TestClass: ival: int = 0 sval: str = '' bval: bool = True fval: float = 1.0 oival: Optional[int] = None osval: Optional[str] = None obval: Optional[bool] = None ofval: Optional[float] = None # Should pass by default. tclass = _TestClass() dataclass_validate(tclass) # No longer valid. tclass.fval = 1 with pytest.raises(TypeError): dataclass_validate(tclass) # Should pass by default. tclass = _TestClass() dataclass_validate(tclass) # No longer valid. # noinspection PyTypeHints tclass.ival = None # type: ignore with pytest.raises(TypeError): dataclass_validate(tclass)
def test_coerce() -> None: """Test value coercion.""" @dataclass class _TestClass: ival: int = 0 fval: float = 0.0 # Float value present for int should never work. obj = _TestClass() # noinspection PyTypeHints obj.ival = 1.0 # type: ignore with pytest.raises(TypeError): dataclass_validate(obj, coerce_to_float=True) with pytest.raises(TypeError): dataclass_validate(obj, coerce_to_float=False) # Int value present for float should work only with coerce on. obj = _TestClass() obj.fval = 1 dataclass_validate(obj, coerce_to_float=True) with pytest.raises(TypeError): dataclass_validate(obj, coerce_to_float=False) # Likewise, passing in an int for a float field should work only # with coerce on. dataclass_from_dict(_TestClass, {'fval': 1}, coerce_to_float=True) with pytest.raises(TypeError): dataclass_from_dict(_TestClass, {'fval': 1}, coerce_to_float=False) # Passing in floats for an int field should never work. with pytest.raises(TypeError): dataclass_from_dict(_TestClass, {'ival': 1.0}, coerce_to_float=True) with pytest.raises(TypeError): dataclass_from_dict(_TestClass, {'ival': 1.0}, coerce_to_float=False)
def config(self, value: ServerConfig) -> None: dataclass_validate(value) self._config = value
def _run_subprocess_until_exit(self) -> None: assert current_thread() is self._subprocess_thread assert self._subprocess is not None assert self._subprocess.stdin is not None # Send the initial server config which should kick things off. # (but make sure its values are still valid first) dataclass_validate(self._config) self._send_server_command(StartServerModeCommand(self._config)) while True: # If the app is trying to shut down, nope out immediately. if self._done: break # Pass along any commands to our process. with self._subprocess_commands_lock: for incmd in self._subprocess_commands: # If we're passing a raw string to exec, no need to wrap it # in any proper structure. if isinstance(incmd, str): self._subprocess.stdin.write((incmd + '\n').encode()) self._subprocess.stdin.flush() else: self._send_server_command(incmd) self._subprocess_commands = [] # Request restarts/shut-downs for various reasons. self._request_shutdowns_or_restarts() # If they want to force-kill our subprocess, simply exit this # loop; the cleanup code will kill the process. if (self._subprocess_force_kill_time is not None and time.time() > self._subprocess_force_kill_time): print(f'{Clr.CYN}Force-killing subprocess...{Clr.RST}') break # Watch for the process exiting on its own.. code: Optional[int] = self._subprocess.poll() if code is not None: if code == 0: clr = Clr.CYN slp = 0.0 desc = '' elif code == 154: clr = Clr.CYN slp = 0.0 desc = ' (idle_exit_minutes reached)' self._wrapper_shutdown_desired = True else: clr = Clr.SRED slp = 5.0 # Avoid super fast death loops. desc = '' print(f'{clr}Server subprocess exited' f' with code {code}{desc}.{Clr.RST}') self._reset_subprocess_vars() time.sleep(slp) break time.sleep(0.25)