confkit
Module that provides the main interface for the confkit package.
It includes the Config class and various data types used for configuration values.
1"""Module that provides the main interface for the confkit package. 2 3It includes the Config class and various data types used for configuration values. 4""" 5 6from .config import Config, ConfigContainerMeta 7from .data_types import ( 8 BaseDataType, 9 Binary, 10 Boolean, 11 Date, 12 DateTime, 13 Dict, 14 Enum, 15 Float, 16 Hex, 17 Integer, 18 IntEnum, 19 IntFlag, 20 List, 21 NoneType, 22 Octal, 23 Optional, 24 Set, 25 StrEnum, 26 String, 27 Time, 28 TimeDelta, 29 Tuple, 30) 31from .exceptions import ConfigPathConflictError, InvalidConverterError, InvalidDefaultError 32 33__all__ = [ 34 "BaseDataType", 35 "Binary", 36 "Boolean", 37 "Config", 38 "ConfigContainerMeta", 39 "ConfigPathConflictError", 40 "Date", 41 "DateTime", 42 "Dict", 43 "Enum", 44 "Float", 45 "Hex", 46 "IntEnum", 47 "IntFlag", 48 "Integer", 49 "InvalidConverterError", 50 "InvalidDefaultError", 51 "List", 52 "NoneType", 53 "Octal", 54 "Optional", 55 "Set", 56 "StrEnum", 57 "String", 58 "Time", 59 "TimeDelta", 60 "Tuple", 61]
19class BaseDataType(ABC, Generic[T]): 20 """Base class used for Config descriptors to define a data type.""" 21 22 def __init__(self, default: T) -> None: 23 """Initialize the base data type.""" 24 self.default = default 25 self.value = default 26 self.type = type(default) 27 28 def __str__(self) -> str: 29 """Return the string representation of the stored value.""" 30 return str(self.value) 31 32 @abstractmethod 33 def convert(self, value: str) -> T: 34 """Convert a string value to the desired type.""" 35 36 def validate(self) -> bool: 37 """Validate that the value matches the expected type.""" 38 orig_bases: tuple[type, ...] | None = getattr(self.__class__, "__orig_bases__", None) 39 40 if not orig_bases: 41 msg = "No type information available for validation." 42 raise InvalidConverterError(msg) 43 44 # Extract type arguments from the generic base 45 for base in orig_bases: 46 if hasattr(base, "__args__"): 47 type_args = base.__args__ 48 if type_args: 49 for type_arg in type_args: 50 if hasattr(type_arg, "__origin__"): 51 # For parameterized generics, check against the origin type 52 if isinstance(self.value, type_arg.__origin__): 53 return True 54 elif isinstance(self.value, (self.type, type_arg)): 55 return True 56 msg = f"Value {self.value} is not any of {type_args}." 57 raise InvalidConverterError(msg) 58 msg = "This should not have raised. Report to the library maintainers with code: `DTBDT`" 59 raise TypeError(msg) 60 61 @staticmethod 62 def cast_optional(default: T | None | BaseDataType[T]) -> BaseDataType[T | None]: 63 """Convert the default value to an Optional data type.""" 64 if default is None: 65 return cast("BaseDataType[T | None]", NoneType()) 66 return Optional(BaseDataType.cast(default)) 67 68 @staticmethod 69 def cast(default: T | BaseDataType[T]) -> BaseDataType[T]: 70 """Convert the default value to a BaseDataType.""" 71 # We use Cast to shut up type checkers, as we know primitive types will be correct. 72 # If a custom type is passed, it should be a BaseDataType subclass, which already has the correct types. 73 match default: 74 case bool(): 75 data_type = cast("BaseDataType[T]", Boolean(default)) 76 case None: 77 data_type = cast("BaseDataType[T]", NoneType()) 78 case int(): 79 data_type = cast("BaseDataType[T]", Integer(default)) 80 case float(): 81 data_type = cast("BaseDataType[T]", Float(default)) 82 case str(): 83 data_type = cast("BaseDataType[T]", String(default)) 84 case BaseDataType(): 85 data_type = default 86 case _: 87 msg = ( 88 f"Unsupported default value type: {type(default).__name__}. " 89 "Use a BaseDataType subclass for custom types." 90 ) 91 raise InvalidDefaultError(msg) 92 return data_type
Base class used for Config descriptors to define a data type.
22 def __init__(self, default: T) -> None: 23 """Initialize the base data type.""" 24 self.default = default 25 self.value = default 26 self.type = type(default)
Initialize the base data type.
32 @abstractmethod 33 def convert(self, value: str) -> T: 34 """Convert a string value to the desired type."""
Convert a string value to the desired type.
36 def validate(self) -> bool: 37 """Validate that the value matches the expected type.""" 38 orig_bases: tuple[type, ...] | None = getattr(self.__class__, "__orig_bases__", None) 39 40 if not orig_bases: 41 msg = "No type information available for validation." 42 raise InvalidConverterError(msg) 43 44 # Extract type arguments from the generic base 45 for base in orig_bases: 46 if hasattr(base, "__args__"): 47 type_args = base.__args__ 48 if type_args: 49 for type_arg in type_args: 50 if hasattr(type_arg, "__origin__"): 51 # For parameterized generics, check against the origin type 52 if isinstance(self.value, type_arg.__origin__): 53 return True 54 elif isinstance(self.value, (self.type, type_arg)): 55 return True 56 msg = f"Value {self.value} is not any of {type_args}." 57 raise InvalidConverterError(msg) 58 msg = "This should not have raised. Report to the library maintainers with code: `DTBDT`" 59 raise TypeError(msg)
Validate that the value matches the expected type.
61 @staticmethod 62 def cast_optional(default: T | None | BaseDataType[T]) -> BaseDataType[T | None]: 63 """Convert the default value to an Optional data type.""" 64 if default is None: 65 return cast("BaseDataType[T | None]", NoneType()) 66 return Optional(BaseDataType.cast(default))
Convert the default value to an Optional data type.
68 @staticmethod 69 def cast(default: T | BaseDataType[T]) -> BaseDataType[T]: 70 """Convert the default value to a BaseDataType.""" 71 # We use Cast to shut up type checkers, as we know primitive types will be correct. 72 # If a custom type is passed, it should be a BaseDataType subclass, which already has the correct types. 73 match default: 74 case bool(): 75 data_type = cast("BaseDataType[T]", Boolean(default)) 76 case None: 77 data_type = cast("BaseDataType[T]", NoneType()) 78 case int(): 79 data_type = cast("BaseDataType[T]", Integer(default)) 80 case float(): 81 data_type = cast("BaseDataType[T]", Float(default)) 82 case str(): 83 data_type = cast("BaseDataType[T]", String(default)) 84 case BaseDataType(): 85 data_type = default 86 case _: 87 msg = ( 88 f"Unsupported default value type: {type(default).__name__}. " 89 "Use a BaseDataType subclass for custom types." 90 ) 91 raise InvalidDefaultError(msg) 92 return data_type
Convert the default value to a BaseDataType.
320class Binary(BaseDataType[bytes | int]): 321 """A config value that represents binary.""" 322 323 def __init__(self, default: bytes | int = 0) -> None: # noqa: D107 324 if isinstance(default, bytes): 325 default = int.from_bytes(default) 326 super().__init__(default) 327 328 def __str__(self) -> str: # noqa: D105 329 if isinstance(self.value, bytes): 330 self.value = int.from_bytes(self.value) 331 return f"0b{self.value:b}" 332 333 def convert(self, value: str) -> int: 334 """Convert a string value to an integer from binary.""" 335 return int(value.removeprefix("0b"), 2)
A config value that represents binary.
236class Boolean(BaseDataType[bool]): 237 """A config value that is a boolean.""" 238 239 def __init__(self, default: bool = False) -> None: # noqa: D107, FBT001, FBT002 240 super().__init__(default) 241 242 def convert(self, value: str) -> bool: 243 """Convert a string value to a boolean.""" 244 if value.lower() in {"true", "1", "yes"}: 245 return True 246 if value.lower() in {"false", "0", "no"}: 247 return False 248 msg = f"Cannot convert {value} to boolean." 249 raise ValueError(msg)
A config value that is a boolean.
239 def __init__(self, default: bool = False) -> None: # noqa: D107, FBT001, FBT002 240 super().__init__(default)
Initialize the base data type.
242 def convert(self, value: str) -> bool: 243 """Convert a string value to a boolean.""" 244 if value.lower() in {"true", "1", "yes"}: 245 return True 246 if value.lower() in {"false", "0", "no"}: 247 return False 248 msg = f"Cannot convert {value} to boolean." 249 raise ValueError(msg)
Convert a string value to a boolean.
48class Config(Generic[VT]): 49 """A descriptor for config values, preserving type information. 50 51 the ValueType (VT) is the type you want the config value to be. 52 """ 53 54 validate_types: ClassVar[bool] = True 55 """Validate that the converter returns the same type as the default value. (not strict)""" 56 write_on_edit: ClassVar[bool] = True 57 """Write to the config file when updating a value.""" 58 optional: bool = False 59 """If True, allows None as an extra type when validating types. (both instance and class variables.)""" 60 61 _parser: ConfkitParser = UNSET 62 _file: Path = UNSET 63 _has_read_config: bool = False 64 65 if TYPE_CHECKING: 66 # Overloads for type checkers to understand the different settings of the Config descriptors. 67 @overload # Custom data type, like Enum's or custom class. 68 def __init__(self, default: BaseDataType[VT]) -> None: ... 69 @overload 70 def __init__(self, default: VT) -> None: ... 71 # Specify the states of optional explicitly for type checkers. 72 @overload 73 def __init__(self: Config[VT], default: VT, *, optional: Literal[False]) -> None: ... 74 @overload 75 def __init__(self: Config[VT], default: BaseDataType[VT], *, optional: Literal[False]) -> None: ... 76 @overload 77 def __init__(self: Config[VT | None], default: VT, *, optional: Literal[True]) -> None: ... 78 @overload 79 def __init__(self: Config[VT | None], default: BaseDataType[VT], *, optional: Literal[True]) -> None: ... 80 81 def __init__( 82 self, 83 default: VT | None | BaseDataType[VT] = UNSET, 84 *, 85 optional: bool = False, 86 ) -> None: 87 """Initialize the config descriptor with a default value. 88 89 Validate that parser and filepath are present. 90 """ 91 cls = self.__class__ 92 self.optional = optional or cls.optional # Be truthy when either one is true. 93 94 if not self.optional and default is UNSET: 95 msg = "Default value cannot be None when optional is False." 96 raise InvalidDefaultError(msg) 97 98 if not self._parser: 99 self._detect_parser() 100 101 self._initialize_data_type(default) 102 self._validate_init() 103 self._read_parser() 104 105 def __init_subclass__(cls) -> None: 106 """Allow for multiple config files/parsers without conflicts.""" 107 super().__init_subclass__() 108 109 parent = cls._find_parent() 110 111 cls.validate_types = parent.validate_types 112 cls.write_on_edit = parent.write_on_edit 113 cls._parser = parent._parser # noqa: SLF001 114 cls._file = parent._file # noqa: SLF001 115 cls._has_read_config = parent._has_read_config # noqa: SLF001 116 117 @classmethod 118 def _find_parent(cls) -> type[Config[Any]]: 119 for base in cls.__bases__: 120 if issubclass(base, Config): 121 parent = base 122 break 123 else: 124 parent = Config 125 return parent 126 127 def _initialize_data_type(self, default: VT | None | BaseDataType[VT]) -> None: 128 """Initialize the data type based on the default value.""" 129 if not self.optional and default is not None: 130 self._data_type = BaseDataType[VT].cast(default) 131 else: 132 self._data_type = BaseDataType[VT].cast_optional(default) 133 134 def _read_parser(self) -> None: 135 """Ensure the parser has read the file at initialization. Avoids rewriting the file when settings are already set.""" 136 cls = self.__class__ 137 if not cls._has_read_config: 138 self._parser.read(self._file) 139 cls._has_read_config = True 140 141 def _validate_init(self) -> None: 142 """Validate the config descriptor, ensuring it's properly set up.""" 143 self.validate_file() 144 self.validate_parser() 145 146 def convert(self, value: str) -> VT: 147 """Convert the value to the desired type using the given converter method.""" 148 # Ignore the type error of VT, type checkers don't like None as an option 149 # We handle it using the `optional` flag, or using Optional DataType. so we can safely ignore it. 150 return self._data_type.convert(value) # type: ignore[reportReturnType] 151 152 @staticmethod 153 def _warn_base_class_usage() -> None: 154 """Warn users that setting parser/file on the base class can lead to unexpected behavior. 155 Tell the user to subclass <Config> first. 156 """ # noqa: D205 157 warnings.warn("<Config> is the base class. Subclass <Config> to avoid unexpected behavior.", stacklevel=2) 158 159 @classmethod 160 @deprecated("Avoid using set_parser. Confkit will automatically assign a parser based on the file extension. In 2.0 this will be a private method.") # noqa: E501 161 def set_parser(cls, parser: ConfkitParser) -> None: 162 """Set the parser for ALL descriptor instances (of this type/class).""" 163 if cls is Config: 164 cls._warn_base_class_usage() 165 cls._parser = parser 166 167 @classmethod 168 def _detect_parser(cls) -> None: 169 """Set the parser for descriptors based on the file extension of cls._file. 170 171 Uses msgspec-based parsers for yaml, json, toml. Defaults to dict structure. 172 Only sets the parser if there is no parser set. 173 """ 174 if cls._file is UNSET: 175 msg = "Config file is not set. Use `set_file()`." 176 raise ValueError(msg) 177 match cls._file.suffix.lower(): 178 case ".ini": 179 cls._parser = IniParser() 180 case ".yaml" | ".yml" | ".json" | ".toml": 181 from confkit.ext.parsers import MsgspecParser # noqa: PLC0415 Only import if actually used. 182 cls._parser = MsgspecParser() 183 case ".env": 184 cls._parser = EnvParser() 185 case _: 186 msg = f"Unsupported config file extension: {cls._file.suffix.lower()}" 187 raise ValueError(msg) 188 189 @classmethod 190 def set_file(cls, file: Path) -> None: 191 """Set the file for ALL descriptors.""" 192 if cls is Config: 193 cls._warn_base_class_usage() 194 cls._file = file 195 cls._watcher = FileWatcher(file) 196 197 def validate_strict_type(self) -> None: 198 """Validate the type of the converter matches the desired type.""" 199 if self._data_type.convert is UNSET: 200 msg = "Converter is not set." 201 raise InvalidConverterError(msg) 202 203 cls = self.__class__ 204 self.__config_value = cls._parser.get(self._section, self._setting) 205 self.__converted_value = self.convert(self.__config_value) 206 207 if not cls.validate_types: 208 return 209 210 self.__converted_type = type(self.__converted_value) 211 default_value_type = type(self._data_type.default) 212 213 is_optional = self.optional or isinstance(self._data_type, Optional) 214 if (is_optional) and self.__converted_type in (default_value_type, NoneType): 215 # Allow None or the same type as the default value to be returned by the converter when _optional is True. 216 return 217 if self.__converted_type is not default_value_type: 218 msg = f"Converter does not return the same type as the default value <{default_value_type}> got <{self.__converted_type}>." # noqa: E501 219 raise InvalidConverterError(msg) 220 221 # Set the data_type value. ensuring validation works as expected. 222 self._data_type.value = self.__converted_value 223 if not self._data_type.validate(): 224 msg = f"Invalid value for {self._section}.{self._setting}: {self.__converted_value}" 225 raise InvalidConverterError(msg) 226 227 @classmethod 228 def validate_file(cls) -> None: 229 """Validate the config file.""" 230 if cls._file is UNSET: 231 msg = f"Config file is not set. use {cls.__name__}.set_file() to set it." 232 raise ValueError(msg) 233 234 @classmethod 235 def validate_parser(cls) -> None: 236 """Validate the config parser.""" 237 if cls._parser is UNSET: 238 msg = f"Config parser is not set. use {cls.__name__}.set_parser() to set it." 239 raise ValueError(msg) 240 241 def __set_name__(self, owner: type, name: str) -> None: 242 """Set the name of the attribute to the name of the descriptor.""" 243 self.name = name 244 self._section = self._build_section_name(owner) 245 self._setting = name 246 self._ensure_option() 247 cls = self.__class__ 248 self._original_value = cls._parser.get(self._section, self._setting) or self._data_type.default 249 self.private = f"_{self._section}_{self._setting}_{self.name}" 250 251 @staticmethod 252 def _build_section_name(owner: type) -> str: 253 """Build a section name from the class hierarchy using dot notation. 254 255 Strips out function-local scope markers like <locals>. 256 """ 257 if qualname := getattr(owner, "__qualname__", None): 258 split_at = qualname.find("<locals>.") 259 if split_at != -1: 260 qualname = qualname[split_at + len("<locals>.") :] 261 return ".".join( 262 part 263 for part in qualname.split(".") 264 ) 265 return owner.__name__ 266 267 def _ensure_section(self) -> None: 268 """Ensure the section exists in the config file. Creates one if it doesn't exist.""" 269 if not self._parser.has_section(self._section): 270 self._parser.add_section(self._section) 271 272 def _ensure_option(self) -> None: 273 """Ensure the option exists in the config file. Creates one if it doesn't exist.""" 274 self._ensure_section() 275 if not self._parser.has_option(self._section, self._setting): 276 cls = self.__class__ 277 cls._set(self._section, self._setting, self._data_type) 278 279 def __get__(self, obj: object, obj_type: object) -> VT: 280 """Get the value of the attribute.""" 281 # obj_type is the class in which the variable is defined 282 # so it can be different than type of VT 283 # but we don't need obj or it's type to get the value from config in our case. 284 if self._watcher.has_changed(): 285 self.on_file_change( 286 "get", 287 self._data_type.value, 288 self.convert(self._parser.get(self._section, self._setting)), 289 ) 290 291 self.validate_strict_type() 292 return self.__converted_value # This is already used when checking type validation, so it's safe to return it. 293 294 def __set__(self, obj: object, value: VT) -> None: 295 """Set the value of the attribute.""" 296 if self._watcher.has_changed(): 297 self.on_file_change("set", self._data_type.value, value) 298 299 self._data_type.value = value 300 cls = self.__class__ 301 cls._set(self._section, self._setting, self._data_type) 302 setattr(obj, self.private, value) 303 304 @classmethod 305 def _set(cls, section: str, setting: str, value: VT | BaseDataType[VT] | BaseDataType[VT | None]) -> None: 306 """Set a config value, and write it to the file.""" 307 if not cls._parser.has_section(section): 308 cls._parser.add_section(section) 309 310 cls._parser.set(section, setting, value) 311 312 if cls.write_on_edit: 313 cls.write() 314 315 316 @classmethod 317 def write(cls) -> None: 318 """Write the config parser to the file.""" 319 cls.validate_file() 320 with cls._file.open("w") as f: 321 cls._parser.write(f) 322 323 @classmethod 324 def set(cls, section: str, setting: str, value: VT) -> Callable[[Callable[P, F]], Callable[P, F]]: 325 """Set a config value using this descriptor.""" 326 327 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 328 @wraps(func) 329 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 330 cls._set(section, setting, value) 331 return func(*args, **kwargs) 332 333 return inner 334 return wrapper 335 336 337 @classmethod 338 def with_setting(cls, setting: Config[OVT]) -> Callable[[Callable[P, F]], Callable[P, F]]: 339 """Insert a config value into **kwargs to the wrapped method/function using this decorator.""" 340 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 341 @wraps(func) 342 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 343 kwargs[setting.name] = setting.convert(cls._parser.get(setting._section, setting._setting)) 344 return func(*args, **kwargs) 345 346 return inner 347 return wrapper 348 349 350 @classmethod 351 def with_kwarg( 352 cls, section: str, setting: str, name: str | None = None, default: VT = UNSET, 353 ) -> Callable[[Callable[P, F]], Callable[P, F]]: 354 """Insert a config value into **kwargs to the wrapped method/function using this descriptor. 355 356 Use kwarg.get(`name`) to get the value. 357 `name` is the name the kwarg gets if passed, if None, it will be the same as `setting`. 358 Section parameter is just for finding the config value. 359 """ 360 if name is None: 361 name = setting 362 if default is UNSET and not cls._parser.has_option(section, setting): 363 msg = f"Config value {section=} {setting=} is not set. and no default value is given." 364 raise ValueError(msg) 365 366 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 367 @wraps(func) 368 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 369 if default is not UNSET: 370 cls._set_default(section, setting, default) 371 kwargs[name] = cls._parser.get(section, setting) 372 return func(*args, **kwargs) 373 374 return inner 375 return wrapper 376 377 @classmethod 378 def _set_default(cls, section: str, setting: str, value: VT) -> None: 379 if cls._parser.get(section, setting, fallback=UNSET) is UNSET: 380 cls._set(section, setting, value) 381 382 @classmethod 383 def default(cls, section: str, setting: str, value: VT) -> Callable[[Callable[P, F]], Callable[P, F]]: 384 """Set a default config value if none are set yet using this descriptor.""" 385 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 386 @wraps(func) 387 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 388 cls._set_default(section, setting, value) 389 return func(*args, **kwargs) 390 391 return inner 392 return wrapper 393 394 @abstractmethod 395 def on_file_change(self, origin: Literal["get", "set"], old: VT | UNSET, new: VT) -> None: 396 """Triggered when the config file changes. 397 398 This needs to be implemented before it's usable. 399 This will be called **before** setting the value from the config file. 400 This will be called **after** getting (but before validating it's type) the value from config file. 401 The `origin` parameter indicates whether the change was triggered by a `get` or `set` operation. 402 """
A descriptor for config values, preserving type information.
the ValueType (VT) is the type you want the config value to be.
81 def __init__( 82 self, 83 default: VT | None | BaseDataType[VT] = UNSET, 84 *, 85 optional: bool = False, 86 ) -> None: 87 """Initialize the config descriptor with a default value. 88 89 Validate that parser and filepath are present. 90 """ 91 cls = self.__class__ 92 self.optional = optional or cls.optional # Be truthy when either one is true. 93 94 if not self.optional and default is UNSET: 95 msg = "Default value cannot be None when optional is False." 96 raise InvalidDefaultError(msg) 97 98 if not self._parser: 99 self._detect_parser() 100 101 self._initialize_data_type(default) 102 self._validate_init() 103 self._read_parser()
Initialize the config descriptor with a default value.
Validate that parser and filepath are present.
Validate that the converter returns the same type as the default value. (not strict)
If True, allows None as an extra type when validating types. (both instance and class variables.)
146 def convert(self, value: str) -> VT: 147 """Convert the value to the desired type using the given converter method.""" 148 # Ignore the type error of VT, type checkers don't like None as an option 149 # We handle it using the `optional` flag, or using Optional DataType. so we can safely ignore it. 150 return self._data_type.convert(value) # type: ignore[reportReturnType]
Convert the value to the desired type using the given converter method.
159 @classmethod 160 @deprecated("Avoid using set_parser. Confkit will automatically assign a parser based on the file extension. In 2.0 this will be a private method.") # noqa: E501 161 def set_parser(cls, parser: ConfkitParser) -> None: 162 """Set the parser for ALL descriptor instances (of this type/class).""" 163 if cls is Config: 164 cls._warn_base_class_usage() 165 cls._parser = parser
Set the parser for ALL descriptor instances (of this type/class).
189 @classmethod 190 def set_file(cls, file: Path) -> None: 191 """Set the file for ALL descriptors.""" 192 if cls is Config: 193 cls._warn_base_class_usage() 194 cls._file = file 195 cls._watcher = FileWatcher(file)
Set the file for ALL descriptors.
197 def validate_strict_type(self) -> None: 198 """Validate the type of the converter matches the desired type.""" 199 if self._data_type.convert is UNSET: 200 msg = "Converter is not set." 201 raise InvalidConverterError(msg) 202 203 cls = self.__class__ 204 self.__config_value = cls._parser.get(self._section, self._setting) 205 self.__converted_value = self.convert(self.__config_value) 206 207 if not cls.validate_types: 208 return 209 210 self.__converted_type = type(self.__converted_value) 211 default_value_type = type(self._data_type.default) 212 213 is_optional = self.optional or isinstance(self._data_type, Optional) 214 if (is_optional) and self.__converted_type in (default_value_type, NoneType): 215 # Allow None or the same type as the default value to be returned by the converter when _optional is True. 216 return 217 if self.__converted_type is not default_value_type: 218 msg = f"Converter does not return the same type as the default value <{default_value_type}> got <{self.__converted_type}>." # noqa: E501 219 raise InvalidConverterError(msg) 220 221 # Set the data_type value. ensuring validation works as expected. 222 self._data_type.value = self.__converted_value 223 if not self._data_type.validate(): 224 msg = f"Invalid value for {self._section}.{self._setting}: {self.__converted_value}" 225 raise InvalidConverterError(msg)
Validate the type of the converter matches the desired type.
227 @classmethod 228 def validate_file(cls) -> None: 229 """Validate the config file.""" 230 if cls._file is UNSET: 231 msg = f"Config file is not set. use {cls.__name__}.set_file() to set it." 232 raise ValueError(msg)
Validate the config file.
234 @classmethod 235 def validate_parser(cls) -> None: 236 """Validate the config parser.""" 237 if cls._parser is UNSET: 238 msg = f"Config parser is not set. use {cls.__name__}.set_parser() to set it." 239 raise ValueError(msg)
Validate the config parser.
316 @classmethod 317 def write(cls) -> None: 318 """Write the config parser to the file.""" 319 cls.validate_file() 320 with cls._file.open("w") as f: 321 cls._parser.write(f)
Write the config parser to the file.
323 @classmethod 324 def set(cls, section: str, setting: str, value: VT) -> Callable[[Callable[P, F]], Callable[P, F]]: 325 """Set a config value using this descriptor.""" 326 327 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 328 @wraps(func) 329 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 330 cls._set(section, setting, value) 331 return func(*args, **kwargs) 332 333 return inner 334 return wrapper
Set a config value using this descriptor.
337 @classmethod 338 def with_setting(cls, setting: Config[OVT]) -> Callable[[Callable[P, F]], Callable[P, F]]: 339 """Insert a config value into **kwargs to the wrapped method/function using this decorator.""" 340 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 341 @wraps(func) 342 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 343 kwargs[setting.name] = setting.convert(cls._parser.get(setting._section, setting._setting)) 344 return func(*args, **kwargs) 345 346 return inner 347 return wrapper
Insert a config value into **kwargs to the wrapped method/function using this decorator.
350 @classmethod 351 def with_kwarg( 352 cls, section: str, setting: str, name: str | None = None, default: VT = UNSET, 353 ) -> Callable[[Callable[P, F]], Callable[P, F]]: 354 """Insert a config value into **kwargs to the wrapped method/function using this descriptor. 355 356 Use kwarg.get(`name`) to get the value. 357 `name` is the name the kwarg gets if passed, if None, it will be the same as `setting`. 358 Section parameter is just for finding the config value. 359 """ 360 if name is None: 361 name = setting 362 if default is UNSET and not cls._parser.has_option(section, setting): 363 msg = f"Config value {section=} {setting=} is not set. and no default value is given." 364 raise ValueError(msg) 365 366 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 367 @wraps(func) 368 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 369 if default is not UNSET: 370 cls._set_default(section, setting, default) 371 kwargs[name] = cls._parser.get(section, setting) 372 return func(*args, **kwargs) 373 374 return inner 375 return wrapper
Insert a config value into **kwargs to the wrapped method/function using this descriptor.
Use kwarg.get(name) to get the value.
name is the name the kwarg gets if passed, if None, it will be the same as setting.
Section parameter is just for finding the config value.
382 @classmethod 383 def default(cls, section: str, setting: str, value: VT) -> Callable[[Callable[P, F]], Callable[P, F]]: 384 """Set a default config value if none are set yet using this descriptor.""" 385 def wrapper(func: Callable[P, F]) -> Callable[P, F]: 386 @wraps(func) 387 def inner(*args: P.args, **kwargs: P.kwargs) -> F: 388 cls._set_default(section, setting, value) 389 return func(*args, **kwargs) 390 391 return inner 392 return wrapper
Set a default config value if none are set yet using this descriptor.
394 @abstractmethod 395 def on_file_change(self, origin: Literal["get", "set"], old: VT | UNSET, new: VT) -> None: 396 """Triggered when the config file changes. 397 398 This needs to be implemented before it's usable. 399 This will be called **before** setting the value from the config file. 400 This will be called **after** getting (but before validating it's type) the value from config file. 401 The `origin` parameter indicates whether the change was triggered by a `get` or `set` operation. 402 """
Triggered when the config file changes.
This needs to be implemented before it's usable.
This will be called before setting the value from the config file.
This will be called after getting (but before validating it's type) the value from config file.
The origin parameter indicates whether the change was triggered by a get or set operation.
37class ConfigContainerMeta(type): 38 """Metaclass for Config to "force" __set__ to be called on class variables.""" 39 40 def __setattr__(cls, key: str, value: object) -> None: 41 """Set the value of the attribute on the class.""" 42 attr = cls.__dict__.get(key) 43 if isinstance(attr, Config): 44 attr.__set__(cls, value) 45 else: 46 super().__setattr__(key, value)
Metaclass for Config to "force" __set__ to be called on class variables.
12class ConfigPathConflictError(ValueError): 13 """Raised when a configuration path conflicts with an existing scalar value. 14 15 This occurs when attempting to treat a scalar value as a section (dict). 16 For example, if "Parent.Value" is a scalar, attempting to set "Parent.Value.Child" 17 would cause this error. 18 """
Raised when a configuration path conflicts with an existing scalar value.
This occurs when attempting to treat a scalar value as a section (dict). For example, if "Parent.Value" is a scalar, attempting to set "Parent.Value.Child" would cause this error.
648class Date(BaseDataType[date]): 649 """A config value that is a date.""" 650 651 @overload 652 def __init__(self, default: date = UNSET) -> None: ... 653 @overload 654 def __init__(self, **kwargs: Unpack[_DateKwargs]) -> None: ... 655 656 def __init__(self, default: date = UNSET, **kwargs: Unpack[_DateKwargs]) -> None: 657 """Initialize the date data type. Defaults to current date if not provided.""" 658 if default is UNSET: 659 default = date(**kwargs) 660 super().__init__(default) 661 662 def convert(self, value: str) -> date: 663 """Convert a string value to a date.""" 664 return date.fromisoformat(value) 665 666 def __str__(self) -> str: # noqa: D105 667 return self.value.isoformat()
A config value that is a date.
656 def __init__(self, default: date = UNSET, **kwargs: Unpack[_DateKwargs]) -> None: 657 """Initialize the date data type. Defaults to current date if not provided.""" 658 if default is UNSET: 659 default = date(**kwargs) 660 super().__init__(default)
Initialize the date data type. Defaults to current date if not provided.
618class DateTime(BaseDataType[datetime]): 619 """A config value that is a datetime.""" 620 621 @overload 622 def __init__(self, default: datetime = UNSET) -> None: ... 623 @overload 624 def __init__(self, **kwargs: Unpack[_DateTimeKwargs]) -> None: ... 625 626 def __init__(self, default: datetime = UNSET, **kwargs: Unpack[_DateTimeKwargs]) -> None: 627 """Initialize the datetime data type. Defaults to current datetime (datetime.now) if not provided.""" 628 if default is UNSET: 629 try: 630 default = datetime(**kwargs) # noqa: DTZ001 Tzinfo is (optionally) passed using kwargs 631 except TypeError: 632 default = datetime.now(tz=UTC) 633 super().__init__(default) 634 635 def convert(self, value: str) -> datetime: 636 """Convert a string value to a datetime.""" 637 return datetime.fromisoformat(value) 638 639 def __str__(self) -> str: 640 """Return the string representation of the stored value.""" 641 return self.value.isoformat()
A config value that is a datetime.
626 def __init__(self, default: datetime = UNSET, **kwargs: Unpack[_DateTimeKwargs]) -> None: 627 """Initialize the datetime data type. Defaults to current datetime (datetime.now) if not provided.""" 628 if default is UNSET: 629 try: 630 default = datetime(**kwargs) # noqa: DTZ001 Tzinfo is (optionally) passed using kwargs 631 except TypeError: 632 default = datetime.now(tz=UTC) 633 super().__init__(default)
Initialize the datetime data type. Defaults to current datetime (datetime.now) if not provided.
524class Dict(BaseDataType[dict[KT, VT]], Generic[KT, VT]): 525 """A config value that is a dictionary of string keys and values of type T.""" 526 527 @overload 528 def __init__(self, default: dict[KT, VT]) -> None: ... 529 @overload 530 def __init__(self, *, key_type: BaseDataType[KT], value_type: BaseDataType[VT]) -> None: ... 531 @overload 532 def __init__( 533 self, 534 default: dict[KT, VT], 535 *, 536 key_type: BaseDataType[KT] = ..., 537 value_type: BaseDataType[VT] = ..., 538 ) -> None: ... 539 540 def __init__( 541 self, 542 default: dict[KT, VT] = UNSET, 543 *, 544 key_type: BaseDataType[KT] = UNSET, 545 value_type: BaseDataType[VT] = UNSET, 546 ) -> None: 547 """Initialize the dict data type.""" 548 if default is UNSET and (key_type is UNSET or value_type is UNSET): 549 msg = "Dict requires either a default with at least one key/value pair, or both key_type and value_type to be specified." # noqa: E501 550 raise InvalidDefaultError(msg) 551 if default is UNSET: 552 default = {} 553 super().__init__(default) 554 555 self._infer_key_type(default, key_type) 556 self._infer_value_type(default, value_type) 557 558 def _infer_key_type(self, default: dict[KT, VT], key_type: BaseDataType[KT]) -> None: 559 """Infer the key type from the default dictionary if not provided.""" 560 if len(default.keys()) <= 0 and key_type is UNSET: 561 msg = "Dict default must have at least one key element to infer type. or specify `key_type=<BaseDataType>`" 562 raise InvalidDefaultError(msg) 563 if key_type is UNSET: 564 for key in default: 565 self._key_data_type = BaseDataType[KT].cast(key) 566 break 567 else: 568 self._key_data_type = key_type 569 570 def _infer_value_type(self, default: dict[KT, VT], value_type: BaseDataType[VT]) -> None: 571 """Infer the value type from the default dictionary if not provided.""" 572 if len(default.values()) <= 0 and value_type is UNSET: 573 msg = "Dict default must have at least one value element to infer type. or specify `value_type=<BaseDataType>`" 574 raise InvalidDefaultError(msg) 575 if value_type is UNSET: 576 for value in default.values(): 577 self._value_data_type = BaseDataType[VT].cast(value) 578 break 579 else: 580 self._value_data_type = value_type 581 582 def convert(self, value: str) -> dict[KT, VT]: 583 """Convert a string to a dictionary.""" 584 if not value: 585 return {} 586 587 parts = value.split(",") 588 result: dict[KT, VT] = {} 589 for part in parts: 590 if "=" not in part: 591 msg = f"Invalid dictionary entry: {part}. Expected format key=value." 592 raise ValueError(msg) 593 key_str, val_str = part.split("=", 1) 594 key = self._key_data_type.convert(key_str.strip()) 595 val = self._value_data_type.convert(val_str.strip()) 596 result[key] = val 597 return result 598 599 def __str__(self) -> str: 600 """Return a string representation of the dictionary.""" 601 items = [ 602 f"{self._key_data_type.convert(str(k))}={self._value_data_type.convert(str(v))}" 603 for k, v in self.value.items() 604 ] 605 return ",".join(items)
A config value that is a dictionary of string keys and values of type T.
540 def __init__( 541 self, 542 default: dict[KT, VT] = UNSET, 543 *, 544 key_type: BaseDataType[KT] = UNSET, 545 value_type: BaseDataType[VT] = UNSET, 546 ) -> None: 547 """Initialize the dict data type.""" 548 if default is UNSET and (key_type is UNSET or value_type is UNSET): 549 msg = "Dict requires either a default with at least one key/value pair, or both key_type and value_type to be specified." # noqa: E501 550 raise InvalidDefaultError(msg) 551 if default is UNSET: 552 default = {} 553 super().__init__(default) 554 555 self._infer_key_type(default, key_type) 556 self._infer_value_type(default, value_type)
Initialize the dict data type.
582 def convert(self, value: str) -> dict[KT, VT]: 583 """Convert a string to a dictionary.""" 584 if not value: 585 return {} 586 587 parts = value.split(",") 588 result: dict[KT, VT] = {} 589 for part in parts: 590 if "=" not in part: 591 msg = f"Invalid dictionary entry: {part}. Expected format key=value." 592 raise ValueError(msg) 593 key_str, val_str = part.split("=", 1) 594 key = self._key_data_type.convert(key_str.strip()) 595 val = self._value_data_type.convert(val_str.strip()) 596 result[key] = val 597 return result
Convert a string to a dictionary.
126class Enum(_EnumBase[EnumType]): 127 """A config value that is an enum.""" 128 129 def convert(self, value: str) -> EnumType: 130 """Convert a string value to an enum.""" 131 value = self._strip_comment(value) 132 parsed_enum_name = value.split(".")[-1] 133 return self.value.__class__[parsed_enum_name] 134 135 def _format_allowed_values(self) -> str: 136 """Format allowed values as comma-separated member names.""" 137 enum_class = self.value.__class__ 138 return ", ".join(member.name for member in enum_class) 139 140 def _get_value_str(self) -> str: 141 """Get the member name.""" 142 return self.value.name
A config value that is an enum.
129 def convert(self, value: str) -> EnumType: 130 """Convert a string value to an enum.""" 131 value = self._strip_comment(value) 132 parsed_enum_name = value.split(".")[-1] 133 return self.value.__class__[parsed_enum_name]
Convert a string value to an enum.
225class Float(BaseDataType[float]): 226 """A config value that is a float.""" 227 228 def __init__(self, default: float = 0.0) -> None: # noqa: D107 229 super().__init__(default) 230 231 def convert(self, value: str) -> float: 232 """Convert a string value to a float.""" 233 return float(value)
A config value that is a float.
294class Hex(Integer): 295 """A config value that represents hexadecimal.""" 296 297 def __init__(self, default: int = 0, base: int = HEXADECIMAL) -> None: # noqa: D107 298 super().__init__(default, base) 299 300 def __str__(self) -> str: # noqa: D105 301 return f"0x{self.value:x}" 302 303 def convert(self, value: str) -> int: 304 """Convert a string value to an integer. from hexadecimal.""" 305 return int(value.removeprefix("0x"), 16)
A config value that represents hexadecimal.
163class IntEnum(_EnumBase[IntEnumType]): 164 """A config value that is an enum.""" 165 166 def convert(self, value: str) -> IntEnumType: 167 """Convert a string value to an enum.""" 168 value = self._strip_comment(value) 169 return self.value.__class__(int(value)) 170 171 def _format_allowed_values(self) -> str: 172 """Format allowed values as comma-separated name(value) pairs.""" 173 enum_class = self.value.__class__ 174 return ", ".join(f"{member.name}({member.value})" for member in enum_class) 175 176 def _get_value_str(self) -> str: 177 """Get the member value as string.""" 178 return str(self.value.value)
A config value that is an enum.
181class IntFlag(_EnumBase[IntFlagType]): 182 """A config value that is an enum.""" 183 184 def convert(self, value: str) -> IntFlagType: 185 """Convert a string value to an enum.""" 186 value = self._strip_comment(value) 187 return self.value.__class__(int(value)) 188 189 def _format_allowed_values(self) -> str: 190 """Format allowed values as comma-separated name(value) pairs.""" 191 enum_class = self.value.__class__ 192 return ", ".join(f"{member.name}({member.value})" for member in enum_class) 193 194 def _get_value_str(self) -> str: 195 """Get the member value as string.""" 196 return str(self.value.value)
A config value that is an enum.
256class Integer(BaseDataType[int]): 257 """A config value that is an integer.""" 258 259 # Define constants for common bases 260 261 def __init__(self, default: int = 0, base: int = DECIMAL) -> None: # noqa: D107 262 super().__init__(default) 263 self.base = base 264 265 @staticmethod 266 def int_to_base(number: int, base: int) -> int: 267 """Convert an integer to a string representation in a given base.""" 268 if number == 0: 269 return 0 270 digits = [] 271 while number: 272 digits.append(str(number % base)) 273 number //= base 274 return int("".join(reversed(digits))) 275 276 def __str__(self) -> str: # noqa: D105 277 if self.base == DECIMAL: 278 return str(self.value) 279 # Convert the base 10 int to base 5 280 self.value = self.int_to_base(int(self.value), self.base) 281 return f"{self.base}c{self.value}" 282 283 def convert(self, value: str) -> int: 284 """Convert a string value to an integer.""" 285 if "c" in value: 286 base_str, val_str = value.split("c") 287 base = int(base_str) 288 if base != self.base: 289 msg = "Base in string does not match base in Integer while converting." 290 raise ValueError(msg) 291 return int(val_str, self.base) 292 return int(value, self.base)
A config value that is an integer.
261 def __init__(self, default: int = 0, base: int = DECIMAL) -> None: # noqa: D107 262 super().__init__(default) 263 self.base = base
Initialize the base data type.
265 @staticmethod 266 def int_to_base(number: int, base: int) -> int: 267 """Convert an integer to a string representation in a given base.""" 268 if number == 0: 269 return 0 270 digits = [] 271 while number: 272 digits.append(str(number % base)) 273 number //= base 274 return int("".join(reversed(digits)))
Convert an integer to a string representation in a given base.
283 def convert(self, value: str) -> int: 284 """Convert a string value to an integer.""" 285 if "c" in value: 286 base_str, val_str = value.split("c") 287 base = int(base_str) 288 if base != self.base: 289 msg = "Base in string does not match base in Integer while converting." 290 raise ValueError(msg) 291 return int(val_str, self.base) 292 return int(value, self.base)
Convert a string value to an integer.
Raised when the converter is not set or invalid.
4class InvalidDefaultError(ValueError): 5 """Raised when the default value is not set or invalid."""
Raised when the default value is not set or invalid.
461class List(_SequenceType[T], Generic[T]): 462 """A config value that is a list of values.""" 463 464 def convert(self, value: str) -> list[T]: 465 """Convert a string to a list.""" 466 return list(super()._convert(value))
A config value that is a list of values.
198class NoneType(BaseDataType[None]): 199 """A config value that is None.""" 200 201 null_values: ClassVar[set[str]] = {"none", "null", "nil"} 202 203 def __init__(self) -> None: 204 """Initialize the NoneType data type.""" 205 super().__init__(None) 206 207 def convert(self, value: str) -> bool: # type: ignore[reportIncompatibleMethodOverride] 208 """Convert a string value to None.""" 209 # Ignore type exception as convert should return True/False for NoneType 210 # to determine if we have a valid null value or not. 211 return value.casefold().strip() in NoneType.null_values
A config value that is None.
203 def __init__(self) -> None: 204 """Initialize the NoneType data type.""" 205 super().__init__(None)
Initialize the NoneType data type.
207 def convert(self, value: str) -> bool: # type: ignore[reportIncompatibleMethodOverride] 208 """Convert a string value to None.""" 209 # Ignore type exception as convert should return True/False for NoneType 210 # to determine if we have a valid null value or not. 211 return value.casefold().strip() in NoneType.null_values
Convert a string value to None.
307class Octal(Integer): 308 """A config value that represents octal.""" 309 310 def __init__(self, default: int = 0, base: int = OCTAL) -> None: # noqa: D107 311 super().__init__(default, base) 312 313 def __str__(self) -> str: # noqa: D105 314 return f"0o{self.value:o}" 315 316 def convert(self, value: str) -> int: 317 """Convert a string value to an integer from octal.""" 318 return int(value.removeprefix("0o"), 8)
A config value that represents octal.
337class Optional(BaseDataType[T | None], Generic[T]): 338 """A config value that is optional, can be None or a specific type.""" 339 340 _none_type = NoneType() 341 342 def __init__(self, data_type: BaseDataType[T]) -> None: 343 """Initialize the optional data type. Wrapping the provided data type.""" 344 self._data_type = data_type 345 346 @property 347 def default(self) -> T | None: 348 """Get the default value of the wrapped data type.""" 349 return self._data_type.default 350 351 @property 352 def value(self) -> T | None: 353 """Get the current value of the wrapped data type.""" 354 return self._data_type.value 355 356 @value.setter 357 def value(self, value: T | None) -> None: 358 """Set the current value of the wrapped data type.""" 359 self._data_type.value = value 360 361 def convert(self, value: str) -> T | None: 362 """Convert a string value to the optional type.""" 363 if self._none_type.convert(value): 364 return None 365 return self._data_type.convert(value) 366 367 def validate(self) -> bool: 368 """Validate that the value is of the wrapped data type or None.""" 369 if self._data_type.value is None: 370 return True 371 return self._data_type.validate() 372 373 def __str__(self) -> str: 374 """Return the string representation of the wrapped data type.""" 375 return str(self._data_type)
A config value that is optional, can be None or a specific type.
342 def __init__(self, data_type: BaseDataType[T]) -> None: 343 """Initialize the optional data type. Wrapping the provided data type.""" 344 self._data_type = data_type
Initialize the optional data type. Wrapping the provided data type.
346 @property 347 def default(self) -> T | None: 348 """Get the default value of the wrapped data type.""" 349 return self._data_type.default
Get the default value of the wrapped data type.
351 @property 352 def value(self) -> T | None: 353 """Get the current value of the wrapped data type.""" 354 return self._data_type.value
Get the current value of the wrapped data type.
475class Set(BaseDataType[set[T]], Generic[T]): 476 """A config value that is a set of values.""" 477 478 @overload 479 def __init__(self, default: set[T]) -> None: ... 480 @overload 481 def __init__(self, *, data_type: BaseDataType[T]) -> None: ... 482 @overload 483 def __init__( 484 self, 485 default: set[T], 486 *, 487 data_type: BaseDataType[T] = ..., 488 ) -> None: ... 489 490 def __init__(self, default: set[T] = UNSET, *, data_type: BaseDataType[T] = UNSET) -> None: 491 """Initialize the set data type.""" 492 if default is UNSET and data_type is UNSET: 493 msg = "Set requires either a default with at least one element, or data_type to be specified." 494 raise InvalidDefaultError(msg) 495 if default is UNSET: 496 default = set() 497 super().__init__(default) 498 self._infer_type(default, data_type) 499 500 def _infer_type(self, default: set[T], data_type: BaseDataType[T]) -> None: 501 if len(default) <= 0 and data_type is UNSET: 502 msg = "Set default must have at least one element to infer type. or specify `data_type=<BaseDataType>`" 503 raise InvalidDefaultError(msg) 504 if data_type is UNSET: 505 sample_element = default.pop() 506 default.add(sample_element) 507 self._data_type = BaseDataType[T].cast(sample_element) 508 else: 509 self._data_type = data_type 510 511 def convert(self, value: str) -> set[T]: 512 """Convert a string to a set.""" 513 if not value: 514 return set() 515 parts = value.split(",") 516 return {self._data_type.convert(item.strip()) for item in parts} 517 518 def __str__(self) -> str: 519 """Return a string representation of the set.""" 520 return ",".join(str(item) for item in self.value)
A config value that is a set of values.
490 def __init__(self, default: set[T] = UNSET, *, data_type: BaseDataType[T] = UNSET) -> None: 491 """Initialize the set data type.""" 492 if default is UNSET and data_type is UNSET: 493 msg = "Set requires either a default with at least one element, or data_type to be specified." 494 raise InvalidDefaultError(msg) 495 if default is UNSET: 496 default = set() 497 super().__init__(default) 498 self._infer_type(default, data_type)
Initialize the set data type.
145class StrEnum(_EnumBase[StrEnumType]): 146 """A config value that is an enum.""" 147 148 def convert(self, value: str) -> StrEnumType: 149 """Convert a string value to an enum.""" 150 value = self._strip_comment(value) 151 return self.value.__class__(value) 152 153 def _format_allowed_values(self) -> str: 154 """Format allowed values as comma-separated member values.""" 155 enum_class = self.value.__class__ 156 return ", ".join(member.value for member in enum_class) 157 158 def _get_value_str(self) -> str: 159 """Get the member value.""" 160 return self.value.value
A config value that is an enum.
214class String(BaseDataType[str]): 215 """A config value that is a string.""" 216 217 def __init__(self, default: str = "") -> None: # noqa: D107 218 super().__init__(default) 219 220 def convert(self, value: str) -> str: 221 """Convert a string value to a string.""" 222 return value
A config value that is a string.
677class Time(BaseDataType[time]): 678 """A config value that is a time.""" 679 680 @overload 681 def __init__(self, default: time = UNSET) -> None: ... 682 @overload 683 def __init__(self, **kwargs: Unpack[_TimeKwargs]) -> None: ... 684 685 def __init__(self, default: time = UNSET, **kwargs: Unpack[_TimeKwargs]) -> None: 686 """Initialize the time data type. Defaults to current time if not provided.""" 687 if default is UNSET: 688 default = time(**kwargs) 689 super().__init__(default) 690 691 def convert(self, value: str) -> time: 692 """Convert a string value to a time.""" 693 return time.fromisoformat(value) 694 695 def __str__(self) -> str: # noqa: D105 696 return self.value.isoformat()
A config value that is a time.
685 def __init__(self, default: time = UNSET, **kwargs: Unpack[_TimeKwargs]) -> None: 686 """Initialize the time data type. Defaults to current time if not provided.""" 687 if default is UNSET: 688 default = time(**kwargs) 689 super().__init__(default)
Initialize the time data type. Defaults to current time if not provided.
707class TimeDelta(BaseDataType[timedelta]): 708 """A config value that is a timedelta.""" 709 710 def __init__( 711 self, 712 default: timedelta = UNSET, 713 **kwargs: Unpack[_TimeDeltaKwargs], 714 ) -> None: 715 """Initialize the timedelta data type. Defaults to 0 if not provided.""" 716 if default is UNSET: 717 default = timedelta(**kwargs) 718 super().__init__(default) 719 720 def convert(self, value: str) -> timedelta: 721 """Convert a string value to a timedelta.""" 722 return timedelta(seconds=float(value)) 723 724 def __str__(self) -> str: # noqa: D105 725 return str(self.value.total_seconds())
A config value that is a timedelta.
710 def __init__( 711 self, 712 default: timedelta = UNSET, 713 **kwargs: Unpack[_TimeDeltaKwargs], 714 ) -> None: 715 """Initialize the timedelta data type. Defaults to 0 if not provided.""" 716 if default is UNSET: 717 default = timedelta(**kwargs) 718 super().__init__(default)
Initialize the timedelta data type. Defaults to 0 if not provided.
468class Tuple(_SequenceType[T], Generic[T]): 469 """A config value that is a tuple of values.""" 470 471 def convert(self, value: str) -> tuple[T, ...]: 472 """Convert a string to a tuple.""" 473 return tuple(super()._convert(value))
A config value that is a tuple of values.