class NmeaInjectorSettingsSpecV1(pykson.JsonObject): kind = pykson.StringField() port = pykson.IntegerField() component_id = pykson.IntegerField() def __eq__(self, other: object) -> Any: if isinstance(other, NmeaInjectorSettingsSpecV1): return self.kind == other.kind and self.port == other.port return False
class BridgeSettingsSpecV1(pykson.JsonObject): serial_path = pykson.StringField() baudrate = pykson.IntegerField() ip = pykson.StringField() udp_port = pykson.IntegerField() @staticmethod def from_spec(spec: "BridgeSpec") -> "BridgeSettingsSpecV1": # type: ignore return BridgeSettingsSpecV1( serial_path=spec.serial_path, baudrate=spec.baud, ip=spec.ip, udp_port=spec.udp_port, ) def __eq__(self, other: object) -> Any: if isinstance(other, BridgeSettingsSpecV1): return self.serial_path == other.serial_path return False
class SettingsV12(SettingsV3): VERSION = 12 version_12_variable = pykson.IntegerField(default_value=1992) def __init__(self, *args: str, **kwargs: int) -> None: super().__init__(*args, **kwargs) self.VERSION = SettingsV12.VERSION def migrate(self, data: Dict[str, Any]) -> None: if data["VERSION"] == SettingsV12.VERSION: return if data["VERSION"] < SettingsV12.VERSION: SettingsV3().migrate(data) data["VERSION"] = SettingsV12.VERSION data["version_12_variable"] = self.version_12_variable
class SettingsV1(settings.BaseSettings): VERSION = 1 animal = pykson.ObjectField(Animal, default_value=Animal("bilica", "dog")) first_variable = pykson.IntegerField(default_value=42) def __init__(self, *args: str, **kwargs: int) -> None: super().__init__(*args, **kwargs) self.VERSION = SettingsV1.VERSION def migrate(self, data: Dict[str, Any]) -> None: if data["VERSION"] == SettingsV1.VERSION: return if data["VERSION"] < SettingsV1.VERSION: super().migrate(data) data["VERSION"] = SettingsV1.VERSION data["animal"] = self.animal data["first_variable"] = self.first_variable
class SettingsV2(settings.BaseSettings): VERSION = 2 first_variable = pykson.IntegerField(default_value=66) new_animal_name = pykson.ObjectField(Animal, default_value=Animal("bilica", "dog")) def __init__(self, *args: str, **kwargs: int) -> None: super().__init__(*args, **kwargs) self.VERSION = SettingsV2.VERSION def migrate(self, data: Dict[str, Any]) -> None: if data["VERSION"] == SettingsV2.VERSION: return if data["VERSION"] < SettingsV2.VERSION: SettingsV1().migrate(data) data["VERSION"] = SettingsV2.VERSION data["first_variable"] = self.first_variable # Update variable name data["new_animal_name"] = data["animal"] data.pop("animal")
class BaseSettings(pykson.JsonObject): """Base settings class that has version control and struct based serialization/deserialization""" VERSION = pykson.IntegerField(default_value=0) def __init__(self, *args: str, **kwargs: int) -> None: super().__init__(*args, **kwargs) @abc.abstractmethod def migrate(self, data: Dict[str, Any]) -> None: """Function used to migrate from previous settings verion Args: data (dict): Data from the previous version settings """ raise RuntimeError("Migrating the setings file does not appears to be possible.") def load(self, file_path: pathlib.Path) -> None: """Load settings from file Args: file_path (pathlib.Path): Path for settings file """ if not file_path.exists(): raise RuntimeError(f"Settings file does not exist: {file_path}") logger.debug(f"Loading settings from file: {file_path}") with open(file_path, encoding="utf-8") as settings_file: result = json.load(settings_file) if "VERSION" not in result.keys(): raise BadSettingsFile(f"Settings file does not appears to contain a valid settings format: {result}") version = result["VERSION"] if version <= 0: raise BadAttributes("Settings file contains invalid version number") if version > self.VERSION: raise SettingsFromTheFuture( f"Settings file comes from a future settings version: {version}, " f"latest supported: {self.VERSION}, tomorrow does not exist" ) if version < self.VERSION: self.migrate(result) version = result["VERSION"] if version != self.VERSION: raise MigrationFail("Migrate chain failed to update to the latest settings version available") # Copy new content to settings class new = Pykson().from_json(result, self.__class__) self.__dict__.update(new.__dict__) def save(self, file_path: pathlib.Path) -> None: """Save settings to file Args: file_path (pathlib.Path): Path for the settings file """ # Path for settings file does not exist, lets ensure that it does parent_path = file_path.parent.absolute() parent_path.mkdir(parents=True, exist_ok=True) with open(file_path, "w", encoding="utf-8") as settings_file: logger.debug(f"Saving settings on: {file_path}") settings_file.write(Pykson().to_json(self)) def reset(self) -> None: """Reset internal data to default values""" logger.debug("Resetting settings") new = self.__class__() self.__dict__.update(new.__dict__)
class SettingsV1Expanded(SettingsV1): new_variable = pykson.IntegerField(default_value=1992)