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 test_validate() -> None: """Testing validation.""" @ioprepped @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 (without coerce) tclass.fval = 1 with pytest.raises(TypeError): dataclass_validate(tclass, coerce_to_float=False) # 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.""" @ioprepped @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 test_datetime_limits() -> None: """Test limiting datetime values in various ways.""" from efro.util import utc_today, utc_this_hour @ioprepped @dataclass class _TestClass: tval: Annotated[datetime.datetime, IOAttrs(whole_hours=True)] # Check whole-hour limit when validating/exporting. obj = _TestClass(tval=utc_this_hour() + datetime.timedelta(minutes=1)) with pytest.raises(ValueError): dataclass_validate(obj) obj.tval = utc_this_hour() dataclass_validate(obj) # Check whole-days limit when importing. out = dataclass_to_dict(obj) out['tval'][-1] += 1 with pytest.raises(ValueError): dataclass_from_dict(_TestClass, out) # Check whole-days limit when validating/exporting. @ioprepped @dataclass class _TestClass2: tval: Annotated[datetime.datetime, IOAttrs(whole_days=True)] obj2 = _TestClass2(tval=utc_today() + datetime.timedelta(hours=1)) with pytest.raises(ValueError): dataclass_validate(obj2) obj2.tval = utc_today() dataclass_validate(obj2) # Check whole-days limit when importing. out = dataclass_to_dict(obj2) out['tval'][-1] += 1 with pytest.raises(ValueError): dataclass_from_dict(_TestClass2, out)
def test_any() -> None: """Test data included with type Any.""" @ioprepped @dataclass class _TestClass: anyval: Any obj = _TestClass(anyval=b'bytes') # JSON output doesn't allow bytes or datetime objects # included in 'Any' data. with pytest.raises(TypeError): dataclass_validate(obj, codec=Codec.JSON) obj.anyval = datetime.datetime.now() with pytest.raises(TypeError): dataclass_validate(obj, codec=Codec.JSON) # Firestore, however, does. obj.anyval = b'bytes' dataclass_validate(obj, codec=Codec.FIRESTORE) obj.anyval = datetime.datetime.now() dataclass_validate(obj, codec=Codec.FIRESTORE)
def config(self, value: ServerConfig) -> None: dataclass_validate(value) self._config = value