msgspec_config
1from .base import DataModel, DataSource, datasources 2from .fields import entry, group 3from .sources import ( 4 APISource, 5 CliSource, 6 DotEnvSource, 7 EnvironSource, 8 JSONSource, 9 TomlSource, 10 YamlSource, 11) 12 13__all__ = ( 14 "DataModel", 15 "DataSource", 16 "datasources", 17 "entry", 18 "group", 19 "APISource", 20 "CliSource", 21 "DotEnvSource", 22 "EnvironSource", 23 "JSONSource", 24 "TomlSource", 25 "YamlSource", 26)
148class DataModel(msgspec.Struct, metaclass=DataModelMeta): 149 """Base typed settings model with optional datasource composition. 150 151 ``DataModel`` instances may be built from ordered datasource patches plus 152 constructor keyword overrides. Source-level unmapped values and unknown 153 constructor kwargs can be inspected with :meth:`get_unmapped_payload`. 154 Raw argv leftovers produced by CLI-aware sources can be inspected with 155 :meth:`get_raw_argv`. 156 """ 157 158 __json_encoder__: ClassVar[msgspec.json.Encoder] = get_json_encoder() 159 __json_decoder__: ClassVar[msgspec.json.Decoder | None] = None 160 __schema__: ClassVar[dict[str, Any] | None] = None 161 __datasource_defs__: ClassVar[tuple["DataSource", ...] | None] = None 162 163 @classmethod 164 def _split_payload_for_convert( 165 cls, 166 payload: Mapping[str, Any], 167 ) -> tuple[dict[str, Any], dict[str, Any]]: 168 """Split payload keys into model-mapped and unmapped dictionaries. 169 170 Args: 171 payload: Raw mapping payload. 172 173 Returns: 174 Tuple ``(mapped, unmapped)`` where ``mapped`` contains only 175 model-recognized keys normalized to encoded field names, and 176 ``unmapped`` contains keys not recognized by ``cls``. 177 """ 178 return split_mapping_by_model_fields(payload, cls) 179 180 @classmethod 181 def _split_constructor_kwargs( 182 cls, 183 payload: Mapping[str, Any], 184 ) -> tuple[dict[str, Any], dict[str, Any]]: 185 """Split constructor kwargs into mapped and unmapped top-level keys. 186 187 Args: 188 payload: Constructor kwargs mapping. 189 190 Returns: 191 Tuple ``(mapped, unmapped)`` where ``mapped`` uses encoded 192 top-level field names. 193 """ 194 return split_top_level_mapping_by_model_fields(payload, cls) 195 196 @classmethod 197 def _prepare_payload_for_convert( 198 cls, 199 payload: Mapping[str, Any], 200 ) -> dict[str, Any]: 201 """Normalize payload keys to model-accepted field names. 202 203 Args: 204 payload: Raw mapping payload. 205 206 Returns: 207 Mapping filtered to recognized model keys and normalized to encoded 208 field names. 209 """ 210 mapped, _ = cls._split_payload_for_convert(payload) 211 return mapped 212 213 @staticmethod 214 def _setup_instance( 215 instance: "DataModel", 216 datasource_instances: tuple["DataSource", ...] | None = None, 217 unmapped_cache: dict[str, Any] | None = None, 218 constructor_unmapped: Mapping[str, Any] | None = None, 219 ) -> "DataModel": 220 """Attach runtime datasource, unmapped, and raw-argv state. 221 222 Args: 223 instance: Model instance to mutate. 224 datasource_instances: Runtime source instances used to build 225 ``instance``. 226 unmapped_cache: Precomputed unmapped cache, or ``None`` to defer 227 computation until unmapped payload access. 228 constructor_unmapped: Unmapped key/value pairs provided directly as 229 constructor kwargs. 230 231 Returns: 232 Same ``instance`` with runtime attributes initialized. 233 ``__raw_argv__`` is populated by concatenating per-source raw argv 234 fragments in source evaluation order. 235 """ 236 if datasource_instances is None: 237 datasource_instances = () 238 instance.__datasource_instances__ = datasource_instances 239 instance.__unmapped_cache__ = unmapped_cache 240 instance.__constructor_unmapped__ = ( 241 constructor_unmapped if constructor_unmapped is not None else {} 242 ) 243 raw_argv: list[str] = [] 244 for datasource in datasource_instances: 245 source_raw_argv = getattr(datasource, "__raw_argv__", None) 246 if isinstance(source_raw_argv, list): 247 raw_argv.extend( 248 item for item in source_raw_argv if isinstance(item, str) 249 ) 250 instance.__raw_argv__ = raw_argv 251 return instance 252 253 @classmethod 254 def from_data(cls, data: dict[str, Any]) -> Self: 255 """Build an instance from a plain mapping payload. 256 257 Args: 258 data: Input mapping to validate against ``cls``. 259 260 Returns: 261 Validated model instance with empty runtime datasource state. 262 Keys not recognized by ``cls`` are ignored. 263 """ 264 prepared = cls._prepare_payload_for_convert(data) 265 instance = msgspec.convert(prepared, type=cls) 266 return cls._setup_instance(instance) 267 268 @classmethod 269 def from_json(cls, json_str: str | bytes) -> Self: 270 """Build an instance from a JSON payload. 271 272 Args: 273 json_str: JSON string or bytes. 274 275 Returns: 276 Decoded model instance with empty runtime datasource state. 277 Keys not recognized by ``cls`` are ignored. 278 279 Raises: 280 TypeError: If decoded JSON is not an object mapping. 281 """ 282 raw = msgspec.json.decode(json_str) 283 if not isinstance(raw, Mapping): 284 raise TypeError("JSON payload must decode to an object mapping") 285 prepared = cls._prepare_payload_for_convert(raw) 286 instance = msgspec.convert(prepared, type=cls) 287 return cls._setup_instance(instance) 288 289 @classmethod 290 def model_json_schema(cls, indent: int = 0) -> str: 291 """Serialize the model JSON schema. 292 293 Args: 294 indent: Pretty-print indentation level. ``0`` disables formatting. 295 296 Returns: 297 JSON schema string representation. 298 """ 299 if cls.__schema__ is None: 300 cls.__schema__ = msgspec.json.schema(cls) 301 json_str = cls.__json_encoder__.encode(cls.__schema__).decode() 302 if indent > 0: 303 return msgspec.json.format(json_str, indent=indent) 304 return json_str 305 306 @classmethod 307 def get_datasources_payload( 308 cls, 309 *datasource_args: "DataSource", 310 **kwargs: Any, 311 ) -> Mapping[str, Any]: 312 """Merge payloads from datasource instances and explicit keyword values. 313 314 Datasources are evaluated left-to-right. Explicit ``kwargs`` are merged 315 last and therefore have highest precedence. 316 317 Args: 318 *datasource_args: Datasource instances to execute. 319 **kwargs: Explicit field overrides. Unknown keys are ignored. 320 321 Returns: 322 Merged mapping ready for ``msgspec.convert``. 323 324 Raises: 325 TypeError: If any datasource returns a non-mapping value. 326 """ 327 prepared_kwargs = cls._prepare_payload_for_convert(kwargs) 328 merged_data, _ = cls._collect_datasources_payload( 329 *datasource_args, **prepared_kwargs 330 ) 331 return merged_data 332 333 @classmethod 334 def _collect_datasources_payload( 335 cls, 336 *datasource_defs: "DataSource", 337 **kwargs: Any, 338 ) -> tuple[dict[str, Any], tuple["DataSource", ...]]: 339 """Clone datasource definitions, load them, and merge payloads. 340 341 Args: 342 *datasource_defs: Datasource template instances bound on the class. 343 **kwargs: Explicit mapped keyword overrides merged after source 344 payloads. 345 346 Returns: 347 Tuple ``(merged_payload, datasource_instances)`` where 348 ``datasource_instances`` are the cloned runtime instances used for 349 this build. 350 351 Raises: 352 TypeError: If any source returns a non-mapping value. 353 """ 354 merged_data: dict[str, Any] = {} 355 datasource_instances: list["DataSource"] = [] 356 357 for datasource_def in datasource_defs: 358 datasource_instance = datasource_def.clone() 359 datasource_data = datasource_instance.resolve(model=cls) 360 if not isinstance(datasource_data, Mapping): 361 raise TypeError( 362 f"DataSource {datasource_def.__class__.__name__} returned a " 363 "non-mapping value" 364 ) 365 366 deep_merge_into(merged_data, datasource_data) 367 datasource_instances.append(datasource_instance) 368 369 if kwargs: 370 deep_merge_into(merged_data, kwargs) 371 372 return merged_data, tuple(datasource_instances) 373 374 def model_dump(self) -> dict[str, Any]: 375 """Serialize this instance into Python builtins. 376 377 Returns: 378 ``dict`` representation containing model field values. 379 """ 380 return msgspec.to_builtins(self) 381 382 def model_dump_json(self, indent: int = 0) -> str: 383 """Serialize this instance to JSON text. 384 385 Args: 386 indent: Pretty-print indentation level. ``0`` disables formatting. 387 388 Returns: 389 JSON string representation of the model. 390 """ 391 json_str = self.__json_encoder__.encode(self).decode() 392 if indent > 0: 393 return msgspec.json.format(json_str, indent=indent) 394 return json_str 395 396 def get_unmapped_payload(self) -> dict[str, Any]: 397 """Return lazily merged unmapped payload from sources and kwargs. 398 399 Returns: 400 Deep-merged mapping of source-level unmapped keys (in source order) 401 plus constructor kwargs that were not recognized by the model. 402 Constructor unmapped keys are merged last. A shallow copy is 403 returned so callers cannot mutate the cache directly. 404 """ 405 cached = getattr(self, "__unmapped_cache__", None) 406 if isinstance(cached, dict): 407 return dict(cached) 408 409 merged: dict[str, Any] = {} 410 datasource_instances = getattr(self, "__datasource_instances__", ()) 411 for datasource in datasource_instances: 412 source_unmapped = getattr(datasource, "__unmapped_kwargs__", None) 413 if isinstance(source_unmapped, Mapping) and source_unmapped: 414 deep_merge_into(merged, source_unmapped) 415 416 constructor_unmapped = getattr(self, "__constructor_unmapped__", None) 417 if isinstance(constructor_unmapped, Mapping) and constructor_unmapped: 418 deep_merge_into(merged, constructor_unmapped) 419 420 self.__unmapped_cache__ = merged 421 return dict(merged) 422 423 def get_raw_argv(self) -> list[str]: 424 """Return CLI argv tokens not consumed by mapped CLI options. 425 426 Returns: 427 Ordered list of leftover CLI tokens collected from runtime 428 datasource instances (for example ``CliSource``). Mapped field 429 options are excluded. A copy is returned. 430 """ 431 raw_argv = getattr(self, "__raw_argv__", None) 432 if isinstance(raw_argv, list): 433 return list(raw_argv) 434 435 merged: list[str] = [] 436 datasource_instances = getattr(self, "__datasource_instances__", ()) 437 for datasource in datasource_instances: 438 source_raw_argv = getattr(datasource, "__raw_argv__", None) 439 if isinstance(source_raw_argv, list): 440 merged.extend(item for item in source_raw_argv if isinstance(item, str)) 441 442 self.__raw_argv__ = merged 443 return list(merged)
Base typed settings model with optional datasource composition.
DataModel instances may be built from ordered datasource patches plus
constructor keyword overrides. Source-level unmapped values and unknown
constructor kwargs can be inspected with get_unmapped_payload().
Raw argv leftovers produced by CLI-aware sources can be inspected with
get_raw_argv().
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
253 @classmethod 254 def from_data(cls, data: dict[str, Any]) -> Self: 255 """Build an instance from a plain mapping payload. 256 257 Args: 258 data: Input mapping to validate against ``cls``. 259 260 Returns: 261 Validated model instance with empty runtime datasource state. 262 Keys not recognized by ``cls`` are ignored. 263 """ 264 prepared = cls._prepare_payload_for_convert(data) 265 instance = msgspec.convert(prepared, type=cls) 266 return cls._setup_instance(instance)
Build an instance from a plain mapping payload.
Arguments:
- data: Input mapping to validate against
cls.
Returns:
Validated model instance with empty runtime datasource state. Keys not recognized by
clsare ignored.
268 @classmethod 269 def from_json(cls, json_str: str | bytes) -> Self: 270 """Build an instance from a JSON payload. 271 272 Args: 273 json_str: JSON string or bytes. 274 275 Returns: 276 Decoded model instance with empty runtime datasource state. 277 Keys not recognized by ``cls`` are ignored. 278 279 Raises: 280 TypeError: If decoded JSON is not an object mapping. 281 """ 282 raw = msgspec.json.decode(json_str) 283 if not isinstance(raw, Mapping): 284 raise TypeError("JSON payload must decode to an object mapping") 285 prepared = cls._prepare_payload_for_convert(raw) 286 instance = msgspec.convert(prepared, type=cls) 287 return cls._setup_instance(instance)
Build an instance from a JSON payload.
Arguments:
- json_str: JSON string or bytes.
Returns:
Decoded model instance with empty runtime datasource state. Keys not recognized by
clsare ignored.
Raises:
- TypeError: If decoded JSON is not an object mapping.
289 @classmethod 290 def model_json_schema(cls, indent: int = 0) -> str: 291 """Serialize the model JSON schema. 292 293 Args: 294 indent: Pretty-print indentation level. ``0`` disables formatting. 295 296 Returns: 297 JSON schema string representation. 298 """ 299 if cls.__schema__ is None: 300 cls.__schema__ = msgspec.json.schema(cls) 301 json_str = cls.__json_encoder__.encode(cls.__schema__).decode() 302 if indent > 0: 303 return msgspec.json.format(json_str, indent=indent) 304 return json_str
Serialize the model JSON schema.
Arguments:
- indent: Pretty-print indentation level.
0disables formatting.
Returns:
JSON schema string representation.
306 @classmethod 307 def get_datasources_payload( 308 cls, 309 *datasource_args: "DataSource", 310 **kwargs: Any, 311 ) -> Mapping[str, Any]: 312 """Merge payloads from datasource instances and explicit keyword values. 313 314 Datasources are evaluated left-to-right. Explicit ``kwargs`` are merged 315 last and therefore have highest precedence. 316 317 Args: 318 *datasource_args: Datasource instances to execute. 319 **kwargs: Explicit field overrides. Unknown keys are ignored. 320 321 Returns: 322 Merged mapping ready for ``msgspec.convert``. 323 324 Raises: 325 TypeError: If any datasource returns a non-mapping value. 326 """ 327 prepared_kwargs = cls._prepare_payload_for_convert(kwargs) 328 merged_data, _ = cls._collect_datasources_payload( 329 *datasource_args, **prepared_kwargs 330 ) 331 return merged_data
Merge payloads from datasource instances and explicit keyword values.
Datasources are evaluated left-to-right. Explicit kwargs are merged
last and therefore have highest precedence.
Arguments:
- *datasource_args: Datasource instances to execute.
- **kwargs: Explicit field overrides. Unknown keys are ignored.
Returns:
Merged mapping ready for
msgspec.convert.
Raises:
- TypeError: If any datasource returns a non-mapping value.
374 def model_dump(self) -> dict[str, Any]: 375 """Serialize this instance into Python builtins. 376 377 Returns: 378 ``dict`` representation containing model field values. 379 """ 380 return msgspec.to_builtins(self)
Serialize this instance into Python builtins.
Returns:
dictrepresentation containing model field values.
382 def model_dump_json(self, indent: int = 0) -> str: 383 """Serialize this instance to JSON text. 384 385 Args: 386 indent: Pretty-print indentation level. ``0`` disables formatting. 387 388 Returns: 389 JSON string representation of the model. 390 """ 391 json_str = self.__json_encoder__.encode(self).decode() 392 if indent > 0: 393 return msgspec.json.format(json_str, indent=indent) 394 return json_str
Serialize this instance to JSON text.
Arguments:
- indent: Pretty-print indentation level.
0disables formatting.
Returns:
JSON string representation of the model.
396 def get_unmapped_payload(self) -> dict[str, Any]: 397 """Return lazily merged unmapped payload from sources and kwargs. 398 399 Returns: 400 Deep-merged mapping of source-level unmapped keys (in source order) 401 plus constructor kwargs that were not recognized by the model. 402 Constructor unmapped keys are merged last. A shallow copy is 403 returned so callers cannot mutate the cache directly. 404 """ 405 cached = getattr(self, "__unmapped_cache__", None) 406 if isinstance(cached, dict): 407 return dict(cached) 408 409 merged: dict[str, Any] = {} 410 datasource_instances = getattr(self, "__datasource_instances__", ()) 411 for datasource in datasource_instances: 412 source_unmapped = getattr(datasource, "__unmapped_kwargs__", None) 413 if isinstance(source_unmapped, Mapping) and source_unmapped: 414 deep_merge_into(merged, source_unmapped) 415 416 constructor_unmapped = getattr(self, "__constructor_unmapped__", None) 417 if isinstance(constructor_unmapped, Mapping) and constructor_unmapped: 418 deep_merge_into(merged, constructor_unmapped) 419 420 self.__unmapped_cache__ = merged 421 return dict(merged)
Return lazily merged unmapped payload from sources and kwargs.
Returns:
Deep-merged mapping of source-level unmapped keys (in source order) plus constructor kwargs that were not recognized by the model. Constructor unmapped keys are merged last. A shallow copy is returned so callers cannot mutate the cache directly.
423 def get_raw_argv(self) -> list[str]: 424 """Return CLI argv tokens not consumed by mapped CLI options. 425 426 Returns: 427 Ordered list of leftover CLI tokens collected from runtime 428 datasource instances (for example ``CliSource``). Mapped field 429 options are excluded. A copy is returned. 430 """ 431 raw_argv = getattr(self, "__raw_argv__", None) 432 if isinstance(raw_argv, list): 433 return list(raw_argv) 434 435 merged: list[str] = [] 436 datasource_instances = getattr(self, "__datasource_instances__", ()) 437 for datasource in datasource_instances: 438 source_raw_argv = getattr(datasource, "__raw_argv__", None) 439 if isinstance(source_raw_argv, list): 440 merged.extend(item for item in source_raw_argv if isinstance(item, str)) 441 442 self.__raw_argv__ = merged 443 return list(merged)
Return CLI argv tokens not consumed by mapped CLI options.
Returns:
Ordered list of leftover CLI tokens collected from runtime datasource instances (for example
CliSource). Mapped field options are excluded. A copy is returned.
446class DataSource(DataModel): 447 """Base class for configuration sources that emit mapping patches. 448 449 Subclasses implement :meth:`load`. Use :meth:`resolve` to run the full 450 source lifecycle (reset, normalize return shape, mapped/unmapped split, 451 and runtime state persistence). 452 """ 453 454 def _normalize_load_result( 455 self, 456 result: Mapping[str, Any] | tuple[Mapping[str, Any], Mapping[str, Any]], 457 ) -> tuple[Mapping[str, Any], Mapping[str, Any] | None]: 458 """Normalize ``load`` output into ``(payload, unmapped_override)``. 459 460 Args: 461 result: Return value from ``load``. 462 463 Returns: 464 Tuple containing payload mapping and optional unmapped override. 465 466 Raises: 467 TypeError: If the return value does not follow the expected shape. 468 """ 469 if isinstance(result, tuple): 470 if len(result) != 2: 471 raise TypeError( 472 f"DataSource {self.__class__.__name__} load() must return " 473 "Mapping or tuple[Mapping, Mapping]" 474 ) 475 payload, unmapped = result 476 if not isinstance(payload, Mapping) or not isinstance(unmapped, Mapping): 477 raise TypeError( 478 f"DataSource {self.__class__.__name__} load() tuple must be " 479 "tuple[Mapping, Mapping]" 480 ) 481 return payload, unmapped 482 483 if not isinstance(result, Mapping): 484 raise TypeError( 485 f"DataSource {self.__class__.__name__} load() must return " 486 "Mapping or tuple[Mapping, Mapping]" 487 ) 488 return result, None 489 490 def _reset_instance(self) -> None: 491 """Reset per-load runtime state on this source instance. 492 493 This clears any previously collected unmapped keys and raw argv 494 leftovers. 495 496 Returns: 497 ``None``. 498 """ 499 self.__unmapped_kwargs__ = {} 500 self.__raw_argv__ = [] 501 502 def _set_unmapped(self, unmapped: Mapping[str, Any]) -> None: 503 """Store unmapped payload values on this source instance. 504 505 Args: 506 unmapped: Mapping of keys that could not be mapped to model fields. 507 508 Returns: 509 ``None``. 510 """ 511 self.__unmapped_kwargs__ = dict(unmapped) 512 513 def _set_raw_argv(self, raw_argv: list[str]) -> None: 514 """Store raw argv leftovers on this source instance. 515 516 Args: 517 raw_argv: Ordered CLI tokens that were not consumed as mapped 518 options for the target model. 519 """ 520 self.__raw_argv__ = list(raw_argv) 521 522 def _split_payload_against_model( 523 self, 524 payload: Mapping[str, Any], 525 model: type[msgspec.Struct] | None, 526 ) -> tuple[dict[str, Any], dict[str, Any]]: 527 """Split one payload into mapped and unmapped sections. 528 529 Args: 530 payload: Raw payload produced by a source. 531 model: Target model type used for key recognition. 532 533 Returns: 534 Tuple ``(mapped, unmapped)``. ``mapped`` uses encoded field names 535 when ``model`` provides aliases. 536 """ 537 if model is None: 538 return dict(payload), {} 539 return split_mapping_by_model_fields(payload, model) 540 541 def _finalize_payload( 542 self, 543 payload: Mapping[str, Any], 544 model: type[msgspec.Struct] | None, 545 unmapped_override: Mapping[str, Any] | None = None, 546 ) -> dict[str, Any]: 547 """Finalize a source payload and persist source unmapped state. 548 549 Args: 550 payload: Raw source payload. 551 model: Target model type used for split logic. 552 unmapped_override: Optional unmapped mapping to merge on top of 553 unmapped values inferred from ``payload``. 554 555 Returns: 556 Model-mapped payload fragment that should be merged into model input. 557 """ 558 mapped, unmapped = self._split_payload_against_model(payload, model) 559 560 if unmapped_override is not None: 561 merged_unmapped = dict(unmapped) 562 deep_merge_into(merged_unmapped, unmapped_override) 563 self._set_unmapped(merged_unmapped) 564 else: 565 self._set_unmapped(unmapped) 566 567 return mapped 568 569 def load( 570 self, 571 model: type[DataModel] | None = None, 572 ) -> Mapping[str, Any] | tuple[Mapping[str, Any], Mapping[str, Any]]: 573 """Return raw source data before mapping finalization. 574 575 Subclasses should override this method to implement source-specific 576 loading behavior. :meth:`resolve` performs instance reset, 577 return-shape normalization, and finalize/split logic. 578 579 Args: 580 model: Optional target model requesting data. 581 582 Returns: 583 Either: 584 - mapping payload to be finalized against ``model``, or 585 - tuple ``(payload, unmapped_override)``. 586 """ 587 raise NotImplementedError 588 589 def resolve(self, model: type[DataModel] | None = None) -> dict[str, Any]: 590 """Load and finalize source data for safe model merging. 591 592 Args: 593 model: Optional target model requesting data. 594 595 Returns: 596 Model-mapped payload fragment ready for merge. When ``model`` is 597 provided, output keys are normalized to encoded field names. 598 """ 599 return self._load(model) 600 601 def _load(self, model: type[DataModel] | None = None) -> dict[str, Any]: 602 """Execute source loading lifecycle for one model resolution. 603 604 This internal wrapper clears per-instance runtime state, calls 605 :meth:`load`, validates the return shape, and finalizes the mapped 606 payload while persisting runtime state. 607 608 Args: 609 model: Optional target model requesting data. 610 611 Returns: 612 Model-mapped payload fragment ready for merge. 613 614 Raises: 615 TypeError: If ``load`` returns an invalid payload shape. 616 """ 617 self._reset_instance() 618 payload, unmapped_override = self._normalize_load_result(self.load(model)) 619 return self._finalize_payload( 620 payload, model, unmapped_override=unmapped_override 621 ) 622 623 def clone(self) -> Self: 624 """Clone this datasource configuration. 625 626 Returns: 627 Deep-copied datasource instance suitable for per-model execution. 628 """ 629 return copy.deepcopy(self)
Base class for configuration sources that emit mapping patches.
Subclasses implement load(). Use resolve() to run the full
source lifecycle (reset, normalize return shape, mapped/unmapped split,
and runtime state persistence).
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
569 def load( 570 self, 571 model: type[DataModel] | None = None, 572 ) -> Mapping[str, Any] | tuple[Mapping[str, Any], Mapping[str, Any]]: 573 """Return raw source data before mapping finalization. 574 575 Subclasses should override this method to implement source-specific 576 loading behavior. :meth:`resolve` performs instance reset, 577 return-shape normalization, and finalize/split logic. 578 579 Args: 580 model: Optional target model requesting data. 581 582 Returns: 583 Either: 584 - mapping payload to be finalized against ``model``, or 585 - tuple ``(payload, unmapped_override)``. 586 """ 587 raise NotImplementedError
Return raw source data before mapping finalization.
Subclasses should override this method to implement source-specific
loading behavior. resolve() performs instance reset,
return-shape normalization, and finalize/split logic.
Arguments:
- model: Optional target model requesting data.
Returns:
Either:
- mapping payload to be finalized against
model, or- tuple
(payload, unmapped_override).
589 def resolve(self, model: type[DataModel] | None = None) -> dict[str, Any]: 590 """Load and finalize source data for safe model merging. 591 592 Args: 593 model: Optional target model requesting data. 594 595 Returns: 596 Model-mapped payload fragment ready for merge. When ``model`` is 597 provided, output keys are normalized to encoded field names. 598 """ 599 return self._load(model)
Load and finalize source data for safe model merging.
Arguments:
- model: Optional target model requesting data.
Returns:
Model-mapped payload fragment ready for merge. When
modelis provided, output keys are normalized to encoded field names.
623 def clone(self) -> Self: 624 """Clone this datasource configuration. 625 626 Returns: 627 Deep-copied datasource instance suitable for per-model execution. 628 """ 629 return copy.deepcopy(self)
Clone this datasource configuration.
Returns:
Deep-copied datasource instance suitable for per-model execution.
632def datasources(*datasource_args: DataSource): 633 """Bind datasource template definitions to a ``DataModel`` class. 634 635 Args: 636 *datasource_args: Datasource templates evaluated during model 637 instantiation. 638 639 Returns: 640 Decorator that writes cloned datasource templates to 641 ``cls.__datasource_defs__``. 642 """ 643 644 def decorator(cls: type[DataModel]) -> type[DataModel]: 645 """Attach datasource templates to one model class. 646 647 Args: 648 cls: Model class being decorated. 649 650 Returns: 651 Same class with datasource definitions attached. 652 """ 653 if datasource_args: 654 cls.__datasource_defs__ = tuple( 655 datasource.clone() for datasource in datasource_args 656 ) 657 else: 658 cls.__datasource_defs__ = None 659 return cls 660 661 return decorator
Bind datasource template definitions to a DataModel class.
Arguments:
- *datasource_args: Datasource templates evaluated during model instantiation.
Returns:
Decorator that writes cloned datasource templates to
cls.__datasource_defs__.
189def entry(value: Any = NODEFAULT, *, name: str | None = None, **kwargs: Any) -> Any: 190 """Declare one model field with optional ``Meta`` kwargs. 191 192 Behavior: 193 - Without extra kwargs, returns ``msgspec.field(...)`` directly. 194 - With extra kwargs, returns an internal sentinel so the metaclass can 195 inject ``Annotated[..., Meta(...)]`` metadata. 196 197 Mutable defaults (``list``, ``dict``, ``set``) are converted to default 198 factories to avoid shared state across instances. 199 200 Args: 201 value: Field default value. 202 name: Optional encoded field name (``msgspec.field(name=...)``). 203 **kwargs: ``msgspec.Meta`` arguments plus extra schema keys 204 (``hidden_if``, ``disabled_if``, ``parent_group``, 205 ``ui_component``), CLI include/exclude key (``cli``), and CLI 206 override keys (``cli_flag``, ``cli_short_flag``). Extra keys are 207 stored under ``Meta.extra_json_schema``. 208 209 Returns: 210 ``msgspec.field`` output or an ``EntryInfo`` sentinel. 211 """ 212 field_kwargs: dict[str, Any] = {} 213 if value is not NODEFAULT: 214 default, default_factory = _to_field_default(value) 215 field_kwargs["default"] = default 216 field_kwargs["default_factory"] = default_factory 217 if name is not None: 218 field_kwargs["name"] = name 219 220 if not kwargs: 221 return field(**field_kwargs) 222 223 return EntryInfo( 224 default=field_kwargs.get("default", NODEFAULT), 225 default_factory=field_kwargs.get("default_factory", NODEFAULT), 226 name=field_kwargs.get("name"), 227 meta_kwargs=kwargs, 228 )
Declare one model field with optional Meta kwargs.
Behavior:
- Without extra kwargs, returns
msgspec.field(...)directly. - With extra kwargs, returns an internal sentinel so the metaclass can
inject
Annotated[..., Meta(...)]metadata.
Mutable defaults (list, dict, set) are converted to default
factories to avoid shared state across instances.
Arguments:
- value: Field default value.
- name: Optional encoded field name (
msgspec.field(name=...)). - **kwargs:
msgspec.Metaarguments plus extra schema keys (hidden_if,disabled_if,parent_group,ui_component), CLI include/exclude key (cli), and CLI override keys (cli_flag,cli_short_flag). Extra keys are stored underMeta.extra_json_schema.
Returns:
msgspec.fieldoutput or anEntryInfosentinel.
350def group(*, collapsed: bool = False, mutable: bool = False) -> GroupInfo: 351 """Declare a grouped field inferred from its type annotation. 352 353 Supported annotation shapes: 354 - object types with zero-argument constructor 355 - ``list[...]`` 356 - ``dict[...]`` 357 358 Args: 359 collapsed: Whether UI consumers should render this group collapsed. 360 mutable: Whether UI consumers should treat this group as mutable. 361 362 Returns: 363 ``GroupInfo`` sentinel consumed during metaclass rewriting. 364 365 Raises: 366 TypeError: If ``collapsed`` or ``mutable`` are not bool. 367 """ 368 if type(collapsed) is not bool: 369 raise TypeError( 370 f"group() 'collapsed' must be bool, got {type(collapsed).__name__}" 371 ) 372 if type(mutable) is not bool: 373 raise TypeError(f"group() 'mutable' must be bool, got {type(mutable).__name__}") 374 return GroupInfo(collapsed=collapsed, mutable=mutable)
Declare a grouped field inferred from its type annotation.
Supported annotation shapes:
- object types with zero-argument constructor
list[...]dict[...]
Arguments:
- collapsed: Whether UI consumers should render this group collapsed.
- mutable: Whether UI consumers should treat this group as mutable.
Returns:
GroupInfosentinel consumed during metaclass rewriting.
Raises:
- TypeError: If
collapsedormutableare not bool.
14class APISource(DataSource): 15 """Load configuration data by calling an HTTP JSON endpoint. 16 17 Attributes: 18 api_url: Endpoint URL used for the GET request. 19 header_name: Optional request header name (for auth, etc.). 20 header_value: Optional request header value. 21 root_node: Optional top-level JSON key to unwrap from response payload. 22 timeout_seconds: Request timeout in seconds. 23 """ 24 25 api_url: str | None = None 26 header_name: str | None = None 27 header_value: str | None = None 28 root_node: str | None = None 29 timeout_seconds: float = 10.0 30 31 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 32 """Fetch and decode endpoint JSON payload. 33 34 Args: 35 model: Optional target model requesting data. Accepted for interface 36 compatibility. 37 38 Returns: 39 Parsed mapping payload, or an empty mapping when URL is unset or 40 selected payload is missing/non-mapping. 41 42 Raises: 43 ValueError: If only one of ``header_name``/``header_value`` is set. 44 RuntimeError: If request fails or response JSON is invalid. 45 """ 46 if not self.api_url: 47 return {} 48 49 if (self.header_name is None) != (self.header_value is None): 50 raise ValueError("header_name and header_value must be set together") 51 52 headers: dict[str, str] = {} 53 if self.header_name is not None and self.header_value is not None: 54 headers[self.header_name] = self.header_value 55 56 request = Request(self.api_url, headers=headers, method="GET") 57 58 try: 59 with urlopen(request, timeout=self.timeout_seconds) as response: 60 raw_data = response.read() 61 except (HTTPError, URLError, TimeoutError, OSError) as exc: 62 raise RuntimeError(f"Failed to fetch API endpoint: {self.api_url}") from exc 63 64 try: 65 data: Any = msgspec.json.decode(raw_data) 66 except Exception as exc: 67 raise RuntimeError( 68 f"Failed to parse API response JSON: {self.api_url}" 69 ) from exc 70 71 if self.root_node: 72 if not isinstance(data, Mapping): 73 return {} 74 data = data.get(self.root_node) 75 76 if not isinstance(data, Mapping): 77 return {} 78 return data
Load configuration data by calling an HTTP JSON endpoint.
Attributes:
- api_url: Endpoint URL used for the GET request.
- header_name: Optional request header name (for auth, etc.).
- header_value: Optional request header value.
- root_node: Optional top-level JSON key to unwrap from response payload.
- timeout_seconds: Request timeout in seconds.
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
31 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 32 """Fetch and decode endpoint JSON payload. 33 34 Args: 35 model: Optional target model requesting data. Accepted for interface 36 compatibility. 37 38 Returns: 39 Parsed mapping payload, or an empty mapping when URL is unset or 40 selected payload is missing/non-mapping. 41 42 Raises: 43 ValueError: If only one of ``header_name``/``header_value`` is set. 44 RuntimeError: If request fails or response JSON is invalid. 45 """ 46 if not self.api_url: 47 return {} 48 49 if (self.header_name is None) != (self.header_value is None): 50 raise ValueError("header_name and header_value must be set together") 51 52 headers: dict[str, str] = {} 53 if self.header_name is not None and self.header_value is not None: 54 headers[self.header_name] = self.header_value 55 56 request = Request(self.api_url, headers=headers, method="GET") 57 58 try: 59 with urlopen(request, timeout=self.timeout_seconds) as response: 60 raw_data = response.read() 61 except (HTTPError, URLError, TimeoutError, OSError) as exc: 62 raise RuntimeError(f"Failed to fetch API endpoint: {self.api_url}") from exc 63 64 try: 65 data: Any = msgspec.json.decode(raw_data) 66 except Exception as exc: 67 raise RuntimeError( 68 f"Failed to parse API response JSON: {self.api_url}" 69 ) from exc 70 71 if self.root_node: 72 if not isinstance(data, Mapping): 73 return {} 74 data = data.get(self.root_node) 75 76 if not isinstance(data, Mapping): 77 return {} 78 return data
Fetch and decode endpoint JSON payload.
Arguments:
- model: Optional target model requesting data. Accepted for interface compatibility.
Returns:
Parsed mapping payload, or an empty mapping when URL is unset or selected payload is missing/non-mapping.
Raises:
- ValueError: If only one of
header_name/header_valueis set. - RuntimeError: If request fails or response JSON is invalid.
363class CliSource(DataSource): 364 """Load configuration values from command-line arguments. 365 366 Attributes: 367 cli_args: Optional argument list. Uses ``sys.argv[1:]`` when unset. 368 autogenerate: Emit generated options for fields without explicit CLI 369 metadata when true. 370 kebab_case: Use kebab-case long flags when true. 371 theme: Rich-click theme name passed via context settings. 372 373 Notes: 374 CLI declarations are generated from model fields and aliases. 375 Per-field behavior can be controlled via 376 ``entry(..., cli=..., cli_flag=..., cli_short_flag=...)`` metadata. 377 CLI tokens not consumed by mapped options are persisted on source 378 runtime state via ``__raw_argv__`` and can be read from the model with 379 ``DataModel.get_raw_argv()``. 380 Precedence is: 381 - ``cli=False``: exclude field. 382 - ``cli=True``: include field. 383 - explicit ``cli_flag``/``cli_short_flag``: include field. 384 - otherwise include based on ``autogenerate``. 385 """ 386 387 cli_args: list[str] | None = None 388 autogenerate: bool = True 389 kebab_case: bool = True 390 theme: str = "cargo-slim" 391 392 def load( 393 self, 394 model: type[msgspec.Struct] | None = None, 395 ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: 396 """Parse command-line options into model-shaped nested data. 397 398 Args: 399 model: Target model used to generate options and coerce values. 400 401 Returns: 402 Either: 403 - nested mapping of explicitly provided values, or 404 - tuple ``(mapped, unmapped)`` when unknown CLI args are present. 405 Mapped keys use encoded field names so payloads can be passed 406 directly to ``msgspec.convert`` for aliased models. 407 Raw unmatched tokens (unknown options and positionals) are always 408 persisted on source runtime state in ``__raw_argv__``. 409 Field inclusion is controlled by ``autogenerate`` and per-field 410 ``cli`` metadata. ``cli_flag`` and ``cli_short_flag`` override 411 emitted declarations for included fields. Automatic short-flag 412 assignment is applied only when ``autogenerate=True``. 413 414 Raises: 415 TypeError: On generated option-name collisions. 416 click.BadParameter: On invalid literal coercion. 417 SystemExit: For help/exit pathways raised by click. 418 """ 419 if model is None: 420 return {} 421 422 flat_with_alias = flatten_model_fields_with_alias(model) 423 if not flat_with_alias: 424 return {} 425 426 path_to_type: dict[str, Any] = {} 427 for canonical_path, field_meta in flat_with_alias.items(): 428 alias_path, field_type = field_meta 429 path_to_type[canonical_path] = field_type 430 path_to_type[alias_path] = field_type 431 432 params: list[click.Parameter] = [] 433 param_to_path: dict[str, str] = {} 434 decl_to_path: dict[str, str] = {} 435 primary_long_flag_by_path: dict[str, str] = {} 436 bool_neg_map: dict[str, str] = {} 437 json_struct_params: dict[str, str] = {} 438 use_kebab = self.kebab_case 439 440 def _register_decl(decl: str, dotted_path: str) -> None: 441 """Track one emitted option declaration and detect collisions. 442 443 Args: 444 decl: Option declaration token such as ``"--host"``. 445 dotted_path: Canonical model field path for ``decl``. 446 447 Returns: 448 ``None``. 449 450 Raises: 451 TypeError: If ``decl`` is already associated with another path. 452 """ 453 existing_path = decl_to_path.get(decl) 454 if existing_path is not None and existing_path != dotted_path: 455 raise TypeError( 456 f"CLI option declaration collision: '{existing_path}' and " 457 f"'{dotted_path}' both map to '{decl}'" 458 ) 459 decl_to_path[decl] = dotted_path 460 461 # Collect top-level Struct fields to expose JSON options and policy. 462 struct_field_names: dict[ 463 str, 464 tuple[str, str, Any, bool | None, str | None, str | None], 465 ] = {} 466 for field_info in struct_fields(model): 467 if get_struct_subtype(field_info.type) is None: 468 continue 469 encode_name = getattr(field_info, "encode_name", field_info.name) 470 if not isinstance(encode_name, str) or not encode_name: 471 encode_name = field_info.name 472 custom_enabled, custom_long, custom_short = _extract_custom_cli_metadata( 473 field_info.type, field_info.name 474 ) 475 struct_field_names[field_info.name] = ( 476 field_info.name, 477 encode_name, 478 field_info.type, 479 custom_enabled, 480 custom_long, 481 custom_short, 482 ) 483 484 emitted_json_opts: set[str] = set() 485 486 ctx_settings: dict[str, Any] = { 487 "help_option_names": ["--help", "-?"], 488 "ignore_unknown_options": True, 489 "allow_extra_args": True, 490 "rich_help_config": { 491 "theme": self.theme, 492 "enable_theme_env_var": False, 493 }, 494 } 495 496 reserved_short: set[str] = set( 497 item.replace("-", "") for item in ctx_settings["help_option_names"] 498 ) 499 assigned_short: set[str] = set() 500 501 for dotted_path, field_meta in flat_with_alias.items(): 502 alias_path, field_type = field_meta 503 top_field = dotted_path.split(".")[0] 504 505 if top_field in struct_field_names and top_field not in emitted_json_opts: 506 emitted_json_opts.add(top_field) 507 ( 508 _, 509 top_alias, 510 _, 511 top_custom_enabled, 512 top_custom_long, 513 top_custom_short, 514 ) = struct_field_names[top_field] 515 include_top_json = _should_include_field( 516 self.autogenerate, 517 top_custom_enabled, 518 top_custom_long, 519 top_custom_short, 520 ) 521 if include_top_json: 522 if top_custom_long is not None: 523 top_long_flags = [top_custom_long] 524 else: 525 top_long_flags = [ 526 _make_flag_name(top_field, kebab_case=use_kebab) 527 ] 528 alias_top_flag = _make_flag_name( 529 top_alias, kebab_case=use_kebab 530 ) 531 if alias_top_flag not in top_long_flags: 532 top_long_flags.append(alias_top_flag) 533 top_long_flags = dedupe_keep_order(top_long_flags) 534 535 json_param_name = top_field.replace(".", "_").replace("-", "_") 536 json_struct_params[json_param_name] = top_alias 537 json_decls = list(top_long_flags) 538 json_decls.append(json_param_name) 539 json_decls = dedupe_keep_order(json_decls) 540 541 if top_custom_short is not None: 542 short = _reserve_short( 543 top_custom_short, 544 reserved_short, 545 assigned_short, 546 top_field, 547 ) 548 elif self.autogenerate: 549 short = _assign_short( 550 top_long_flags[0], reserved_short, assigned_short 551 ) 552 else: 553 short = None 554 if short is not None: 555 json_decls.insert(0, "-" + short) 556 557 for decl in json_decls: 558 if decl.startswith("-"): 559 _register_decl(decl, top_field) 560 561 params.append( 562 click.Option( 563 param_decls=json_decls, 564 type=click.STRING, 565 help="JSON string", 566 ) 567 ) 568 569 top_struct = struct_field_names.get(top_field) 570 if top_struct is not None: 571 _, _, _, top_custom_enabled, _, _ = top_struct 572 if top_custom_enabled is False: 573 continue 574 575 custom_enabled, custom_long, custom_short = _extract_custom_cli_metadata( 576 field_type, dotted_path 577 ) 578 include_leaf = _should_include_field( 579 self.autogenerate, 580 custom_enabled, 581 custom_long, 582 custom_short, 583 ) 584 if not include_leaf: 585 continue 586 587 if custom_long is not None: 588 long_flags = [custom_long] 589 else: 590 long_flags = [_make_flag_name(dotted_path, kebab_case=use_kebab)] 591 alias_flag = _make_flag_name(alias_path, kebab_case=use_kebab) 592 if alias_flag not in long_flags: 593 long_flags.append(alias_flag) 594 long_flags = dedupe_keep_order(long_flags) 595 596 param_name = dotted_path.replace(".", "_").replace("-", "_") 597 existing_path = param_to_path.get(param_name) 598 if existing_path is not None and existing_path != alias_path: 599 raise TypeError( 600 f"CLI option name collision: '{existing_path}' and " 601 f"'{alias_path}' both map to '{param_name}'" 602 ) 603 param_to_path[param_name] = alias_path 604 primary_long_flag_by_path[dotted_path] = long_flags[0] 605 primary_long_flag_by_path[alias_path] = long_flags[0] 606 607 for decl in long_flags: 608 _register_decl(decl, dotted_path) 609 610 click_kwargs = _python_type_to_click(field_type) 611 is_bool_flag = bool(click_kwargs.pop("is_bool_flag", False)) 612 613 if is_bool_flag: 614 pos_decls = list(long_flags) 615 pos_decls.append(param_name) 616 if custom_short is not None: 617 short = _reserve_short( 618 custom_short, 619 reserved_short, 620 assigned_short, 621 dotted_path, 622 ) 623 pos_decls.insert(0, "-" + short) 624 elif self.autogenerate and "." not in dotted_path: 625 short = _assign_short(long_flags[0], reserved_short, assigned_short) 626 if short is not None: 627 pos_decls.insert(0, "-" + short) 628 pos_decls = dedupe_keep_order(pos_decls) 629 params.append( 630 click.Option( 631 param_decls=pos_decls, 632 is_flag=True, 633 flag_value=True, 634 ) 635 ) 636 637 neg_param_name = "no_" + param_name 638 existing_neg = bool_neg_map.get(neg_param_name) 639 if existing_neg is not None and existing_neg != alias_path: 640 raise TypeError( 641 f"CLI bool negation collision: '{existing_neg}' and " 642 f"'{alias_path}' both map to '{neg_param_name}'" 643 ) 644 bool_neg_map[neg_param_name] = alias_path 645 646 neg_decls = [f"--no-{flag[2:]}" for flag in long_flags] 647 neg_decls.append(neg_param_name) 648 neg_decls = dedupe_keep_order(neg_decls) 649 for decl in neg_decls: 650 _register_decl(decl, dotted_path) 651 params.append( 652 click.Option( 653 param_decls=neg_decls, 654 is_flag=True, 655 flag_value=True, 656 hidden=True, 657 ) 658 ) 659 continue 660 661 click_kwargs.setdefault("default", None) 662 click_kwargs["required"] = False 663 664 decls = list(long_flags) 665 decls.append(param_name) 666 if custom_short is not None: 667 short = _reserve_short( 668 custom_short, 669 reserved_short, 670 assigned_short, 671 dotted_path, 672 ) 673 decls.insert(0, "-" + short) 674 elif self.autogenerate and "." not in dotted_path: 675 short = _assign_short(long_flags[0], reserved_short, assigned_short) 676 if short is not None: 677 decls.insert(0, "-" + short) 678 decls = dedupe_keep_order(decls) 679 680 help_text = click_kwargs.pop("help", None) or "" 681 params.append( 682 click.Option( 683 param_decls=decls, 684 help=help_text or None, 685 **click_kwargs, 686 ) 687 ) 688 689 command_name = _resolve_command_name() 690 691 epilog_parts: list[str] = [] 692 if bool_neg_map: 693 epilog_parts.append( 694 "Untyped flags (toggles) can be negated with the --no- prefix " 695 "(e.g. --no-debug)." 696 ) 697 if json_struct_params: 698 epilog_parts.append( 699 "Nested options accept a JSON string " 700 '(e.g. --log \'{"level": "DEBUG"}\').' 701 ) 702 703 command = click.RichCommand( 704 name=command_name, 705 params=params, 706 context_settings=ctx_settings, 707 epilog="\n\n".join(epilog_parts) if epilog_parts else None, 708 ) 709 710 args = list(self.cli_args) if self.cli_args is not None else sys.argv[1:] 711 712 try: 713 ctx: click.Context = command.make_context(command_name, list(args)) 714 except click.exceptions.Exit as exc: 715 raise SystemExit(getattr(exc, "code", 0)) from None 716 717 extra_args = list(ctx.args) 718 self._set_raw_argv(extra_args) 719 720 raw_values: dict[str, Any] = ctx.params 721 result: dict[str, Any] = {} 722 723 # Pass 1: struct JSON options. 724 for param_name, value in raw_values.items(): 725 if value is None or param_name not in json_struct_params: 726 continue 727 source = ctx.get_parameter_source(param_name) 728 if source is not None and source != click.core.ParameterSource.COMMANDLINE: 729 continue 730 decoded = try_json_decode(value) 731 if decoded is not _COERCE_FAILED and isinstance(decoded, dict): 732 set_nested(result, json_struct_params[param_name], decoded) 733 734 # Pass 2: scalar fields + bool negations (override JSON subkeys). 735 for param_name, value in raw_values.items(): 736 if value is None or param_name in json_struct_params: 737 continue 738 source = ctx.get_parameter_source(param_name) 739 if source is not None and source != click.core.ParameterSource.COMMANDLINE: 740 continue 741 742 neg_path = bool_neg_map.get(param_name) 743 if neg_path is not None: 744 set_nested(result, neg_path, False) 745 continue 746 747 dotted_path = param_to_path.get(param_name) 748 if dotted_path is None: 749 continue 750 751 field_type = path_to_type.get(dotted_path) 752 if field_type is not None and isinstance(value, str): 753 unwrapped = unwrap_annotated(field_type) 754 coerced = coerce_env_value(value, field_type) 755 if coerced is _COERCE_FAILED and get_origin(unwrapped) is Literal: 756 allowed = ", ".join(repr(item) for item in get_args(unwrapped)) 757 param_hint = primary_long_flag_by_path.get( 758 dotted_path, 759 _make_flag_name(dotted_path, kebab_case=use_kebab), 760 ) 761 raise click.BadParameter( 762 f"invalid literal value {value!r}; allowed values: {allowed}", 763 param_hint=param_hint, 764 ) 765 if coerced is not _COERCE_FAILED: 766 value = coerced 767 768 set_nested(result, dotted_path, value) 769 770 if extra_args: 771 return result, _parse_unmapped_cli_args(extra_args) 772 return result
Load configuration values from command-line arguments.
Attributes:
- cli_args: Optional argument list. Uses
sys.argv[1:]when unset. - autogenerate: Emit generated options for fields without explicit CLI metadata when true.
- kebab_case: Use kebab-case long flags when true.
- theme: Rich-click theme name passed via context settings.
Notes:
CLI declarations are generated from model fields and aliases. Per-field behavior can be controlled via
entry(..., cli=..., cli_flag=..., cli_short_flag=...)metadata. CLI tokens not consumed by mapped options are persisted on source runtime state via__raw_argv__and can be read from the model withDataModel.get_raw_argv(). Precedence is:
cli=False: exclude field.cli=True: include field.- explicit
cli_flag/cli_short_flag: include field.- otherwise include based on
autogenerate.
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
392 def load( 393 self, 394 model: type[msgspec.Struct] | None = None, 395 ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: 396 """Parse command-line options into model-shaped nested data. 397 398 Args: 399 model: Target model used to generate options and coerce values. 400 401 Returns: 402 Either: 403 - nested mapping of explicitly provided values, or 404 - tuple ``(mapped, unmapped)`` when unknown CLI args are present. 405 Mapped keys use encoded field names so payloads can be passed 406 directly to ``msgspec.convert`` for aliased models. 407 Raw unmatched tokens (unknown options and positionals) are always 408 persisted on source runtime state in ``__raw_argv__``. 409 Field inclusion is controlled by ``autogenerate`` and per-field 410 ``cli`` metadata. ``cli_flag`` and ``cli_short_flag`` override 411 emitted declarations for included fields. Automatic short-flag 412 assignment is applied only when ``autogenerate=True``. 413 414 Raises: 415 TypeError: On generated option-name collisions. 416 click.BadParameter: On invalid literal coercion. 417 SystemExit: For help/exit pathways raised by click. 418 """ 419 if model is None: 420 return {} 421 422 flat_with_alias = flatten_model_fields_with_alias(model) 423 if not flat_with_alias: 424 return {} 425 426 path_to_type: dict[str, Any] = {} 427 for canonical_path, field_meta in flat_with_alias.items(): 428 alias_path, field_type = field_meta 429 path_to_type[canonical_path] = field_type 430 path_to_type[alias_path] = field_type 431 432 params: list[click.Parameter] = [] 433 param_to_path: dict[str, str] = {} 434 decl_to_path: dict[str, str] = {} 435 primary_long_flag_by_path: dict[str, str] = {} 436 bool_neg_map: dict[str, str] = {} 437 json_struct_params: dict[str, str] = {} 438 use_kebab = self.kebab_case 439 440 def _register_decl(decl: str, dotted_path: str) -> None: 441 """Track one emitted option declaration and detect collisions. 442 443 Args: 444 decl: Option declaration token such as ``"--host"``. 445 dotted_path: Canonical model field path for ``decl``. 446 447 Returns: 448 ``None``. 449 450 Raises: 451 TypeError: If ``decl`` is already associated with another path. 452 """ 453 existing_path = decl_to_path.get(decl) 454 if existing_path is not None and existing_path != dotted_path: 455 raise TypeError( 456 f"CLI option declaration collision: '{existing_path}' and " 457 f"'{dotted_path}' both map to '{decl}'" 458 ) 459 decl_to_path[decl] = dotted_path 460 461 # Collect top-level Struct fields to expose JSON options and policy. 462 struct_field_names: dict[ 463 str, 464 tuple[str, str, Any, bool | None, str | None, str | None], 465 ] = {} 466 for field_info in struct_fields(model): 467 if get_struct_subtype(field_info.type) is None: 468 continue 469 encode_name = getattr(field_info, "encode_name", field_info.name) 470 if not isinstance(encode_name, str) or not encode_name: 471 encode_name = field_info.name 472 custom_enabled, custom_long, custom_short = _extract_custom_cli_metadata( 473 field_info.type, field_info.name 474 ) 475 struct_field_names[field_info.name] = ( 476 field_info.name, 477 encode_name, 478 field_info.type, 479 custom_enabled, 480 custom_long, 481 custom_short, 482 ) 483 484 emitted_json_opts: set[str] = set() 485 486 ctx_settings: dict[str, Any] = { 487 "help_option_names": ["--help", "-?"], 488 "ignore_unknown_options": True, 489 "allow_extra_args": True, 490 "rich_help_config": { 491 "theme": self.theme, 492 "enable_theme_env_var": False, 493 }, 494 } 495 496 reserved_short: set[str] = set( 497 item.replace("-", "") for item in ctx_settings["help_option_names"] 498 ) 499 assigned_short: set[str] = set() 500 501 for dotted_path, field_meta in flat_with_alias.items(): 502 alias_path, field_type = field_meta 503 top_field = dotted_path.split(".")[0] 504 505 if top_field in struct_field_names and top_field not in emitted_json_opts: 506 emitted_json_opts.add(top_field) 507 ( 508 _, 509 top_alias, 510 _, 511 top_custom_enabled, 512 top_custom_long, 513 top_custom_short, 514 ) = struct_field_names[top_field] 515 include_top_json = _should_include_field( 516 self.autogenerate, 517 top_custom_enabled, 518 top_custom_long, 519 top_custom_short, 520 ) 521 if include_top_json: 522 if top_custom_long is not None: 523 top_long_flags = [top_custom_long] 524 else: 525 top_long_flags = [ 526 _make_flag_name(top_field, kebab_case=use_kebab) 527 ] 528 alias_top_flag = _make_flag_name( 529 top_alias, kebab_case=use_kebab 530 ) 531 if alias_top_flag not in top_long_flags: 532 top_long_flags.append(alias_top_flag) 533 top_long_flags = dedupe_keep_order(top_long_flags) 534 535 json_param_name = top_field.replace(".", "_").replace("-", "_") 536 json_struct_params[json_param_name] = top_alias 537 json_decls = list(top_long_flags) 538 json_decls.append(json_param_name) 539 json_decls = dedupe_keep_order(json_decls) 540 541 if top_custom_short is not None: 542 short = _reserve_short( 543 top_custom_short, 544 reserved_short, 545 assigned_short, 546 top_field, 547 ) 548 elif self.autogenerate: 549 short = _assign_short( 550 top_long_flags[0], reserved_short, assigned_short 551 ) 552 else: 553 short = None 554 if short is not None: 555 json_decls.insert(0, "-" + short) 556 557 for decl in json_decls: 558 if decl.startswith("-"): 559 _register_decl(decl, top_field) 560 561 params.append( 562 click.Option( 563 param_decls=json_decls, 564 type=click.STRING, 565 help="JSON string", 566 ) 567 ) 568 569 top_struct = struct_field_names.get(top_field) 570 if top_struct is not None: 571 _, _, _, top_custom_enabled, _, _ = top_struct 572 if top_custom_enabled is False: 573 continue 574 575 custom_enabled, custom_long, custom_short = _extract_custom_cli_metadata( 576 field_type, dotted_path 577 ) 578 include_leaf = _should_include_field( 579 self.autogenerate, 580 custom_enabled, 581 custom_long, 582 custom_short, 583 ) 584 if not include_leaf: 585 continue 586 587 if custom_long is not None: 588 long_flags = [custom_long] 589 else: 590 long_flags = [_make_flag_name(dotted_path, kebab_case=use_kebab)] 591 alias_flag = _make_flag_name(alias_path, kebab_case=use_kebab) 592 if alias_flag not in long_flags: 593 long_flags.append(alias_flag) 594 long_flags = dedupe_keep_order(long_flags) 595 596 param_name = dotted_path.replace(".", "_").replace("-", "_") 597 existing_path = param_to_path.get(param_name) 598 if existing_path is not None and existing_path != alias_path: 599 raise TypeError( 600 f"CLI option name collision: '{existing_path}' and " 601 f"'{alias_path}' both map to '{param_name}'" 602 ) 603 param_to_path[param_name] = alias_path 604 primary_long_flag_by_path[dotted_path] = long_flags[0] 605 primary_long_flag_by_path[alias_path] = long_flags[0] 606 607 for decl in long_flags: 608 _register_decl(decl, dotted_path) 609 610 click_kwargs = _python_type_to_click(field_type) 611 is_bool_flag = bool(click_kwargs.pop("is_bool_flag", False)) 612 613 if is_bool_flag: 614 pos_decls = list(long_flags) 615 pos_decls.append(param_name) 616 if custom_short is not None: 617 short = _reserve_short( 618 custom_short, 619 reserved_short, 620 assigned_short, 621 dotted_path, 622 ) 623 pos_decls.insert(0, "-" + short) 624 elif self.autogenerate and "." not in dotted_path: 625 short = _assign_short(long_flags[0], reserved_short, assigned_short) 626 if short is not None: 627 pos_decls.insert(0, "-" + short) 628 pos_decls = dedupe_keep_order(pos_decls) 629 params.append( 630 click.Option( 631 param_decls=pos_decls, 632 is_flag=True, 633 flag_value=True, 634 ) 635 ) 636 637 neg_param_name = "no_" + param_name 638 existing_neg = bool_neg_map.get(neg_param_name) 639 if existing_neg is not None and existing_neg != alias_path: 640 raise TypeError( 641 f"CLI bool negation collision: '{existing_neg}' and " 642 f"'{alias_path}' both map to '{neg_param_name}'" 643 ) 644 bool_neg_map[neg_param_name] = alias_path 645 646 neg_decls = [f"--no-{flag[2:]}" for flag in long_flags] 647 neg_decls.append(neg_param_name) 648 neg_decls = dedupe_keep_order(neg_decls) 649 for decl in neg_decls: 650 _register_decl(decl, dotted_path) 651 params.append( 652 click.Option( 653 param_decls=neg_decls, 654 is_flag=True, 655 flag_value=True, 656 hidden=True, 657 ) 658 ) 659 continue 660 661 click_kwargs.setdefault("default", None) 662 click_kwargs["required"] = False 663 664 decls = list(long_flags) 665 decls.append(param_name) 666 if custom_short is not None: 667 short = _reserve_short( 668 custom_short, 669 reserved_short, 670 assigned_short, 671 dotted_path, 672 ) 673 decls.insert(0, "-" + short) 674 elif self.autogenerate and "." not in dotted_path: 675 short = _assign_short(long_flags[0], reserved_short, assigned_short) 676 if short is not None: 677 decls.insert(0, "-" + short) 678 decls = dedupe_keep_order(decls) 679 680 help_text = click_kwargs.pop("help", None) or "" 681 params.append( 682 click.Option( 683 param_decls=decls, 684 help=help_text or None, 685 **click_kwargs, 686 ) 687 ) 688 689 command_name = _resolve_command_name() 690 691 epilog_parts: list[str] = [] 692 if bool_neg_map: 693 epilog_parts.append( 694 "Untyped flags (toggles) can be negated with the --no- prefix " 695 "(e.g. --no-debug)." 696 ) 697 if json_struct_params: 698 epilog_parts.append( 699 "Nested options accept a JSON string " 700 '(e.g. --log \'{"level": "DEBUG"}\').' 701 ) 702 703 command = click.RichCommand( 704 name=command_name, 705 params=params, 706 context_settings=ctx_settings, 707 epilog="\n\n".join(epilog_parts) if epilog_parts else None, 708 ) 709 710 args = list(self.cli_args) if self.cli_args is not None else sys.argv[1:] 711 712 try: 713 ctx: click.Context = command.make_context(command_name, list(args)) 714 except click.exceptions.Exit as exc: 715 raise SystemExit(getattr(exc, "code", 0)) from None 716 717 extra_args = list(ctx.args) 718 self._set_raw_argv(extra_args) 719 720 raw_values: dict[str, Any] = ctx.params 721 result: dict[str, Any] = {} 722 723 # Pass 1: struct JSON options. 724 for param_name, value in raw_values.items(): 725 if value is None or param_name not in json_struct_params: 726 continue 727 source = ctx.get_parameter_source(param_name) 728 if source is not None and source != click.core.ParameterSource.COMMANDLINE: 729 continue 730 decoded = try_json_decode(value) 731 if decoded is not _COERCE_FAILED and isinstance(decoded, dict): 732 set_nested(result, json_struct_params[param_name], decoded) 733 734 # Pass 2: scalar fields + bool negations (override JSON subkeys). 735 for param_name, value in raw_values.items(): 736 if value is None or param_name in json_struct_params: 737 continue 738 source = ctx.get_parameter_source(param_name) 739 if source is not None and source != click.core.ParameterSource.COMMANDLINE: 740 continue 741 742 neg_path = bool_neg_map.get(param_name) 743 if neg_path is not None: 744 set_nested(result, neg_path, False) 745 continue 746 747 dotted_path = param_to_path.get(param_name) 748 if dotted_path is None: 749 continue 750 751 field_type = path_to_type.get(dotted_path) 752 if field_type is not None and isinstance(value, str): 753 unwrapped = unwrap_annotated(field_type) 754 coerced = coerce_env_value(value, field_type) 755 if coerced is _COERCE_FAILED and get_origin(unwrapped) is Literal: 756 allowed = ", ".join(repr(item) for item in get_args(unwrapped)) 757 param_hint = primary_long_flag_by_path.get( 758 dotted_path, 759 _make_flag_name(dotted_path, kebab_case=use_kebab), 760 ) 761 raise click.BadParameter( 762 f"invalid literal value {value!r}; allowed values: {allowed}", 763 param_hint=param_hint, 764 ) 765 if coerced is not _COERCE_FAILED: 766 value = coerced 767 768 set_nested(result, dotted_path, value) 769 770 if extra_args: 771 return result, _parse_unmapped_cli_args(extra_args) 772 return result
Parse command-line options into model-shaped nested data.
Arguments:
- model: Target model used to generate options and coerce values.
Returns:
Either:
- nested mapping of explicitly provided values, or
- tuple
(mapped, unmapped)when unknown CLI args are present. Mapped keys use encoded field names so payloads can be passed directly tomsgspec.convertfor aliased models. Raw unmatched tokens (unknown options and positionals) are always persisted on source runtime state in__raw_argv__. Field inclusion is controlled byautogenerateand per-fieldclimetadata.cli_flagandcli_short_flagoverride emitted declarations for included fields. Automatic short-flag assignment is applied only whenautogenerate=True.
Raises:
- TypeError: On generated option-name collisions.
- click.BadParameter: On invalid literal coercion.
- SystemExit: For help/exit pathways raised by click.
127class DotEnvSource(DataSource): 128 """Load configuration data from a dotenv file. 129 130 Attributes: 131 dotenv_path: Dotenv file path. 132 dotenv_encoding: Text encoding used to read the file. 133 env_prefix: Required prefix used to filter keys (MANDATORY!). 134 nested_separator: Separator used to represent nesting in keys. 135 """ 136 137 dotenv_path: str = ".env" 138 dotenv_encoding: str = "utf-8" 139 env_prefix: str = "" 140 nested_separator: str = "_" 141 142 def load( 143 self, 144 model: type[msgspec.Struct] | None = None, 145 ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: 146 """Read dotenv variables from file and map them to config data. 147 148 Args: 149 model: Optional target model used for key resolution and coercion. 150 151 Returns: 152 Mapping of model-recognized values. When ``model`` is ``None``, 153 returns a flat lowercase mapping. With ``model``, field resolution 154 accepts canonical and encoded names, and mapped keys are emitted as 155 encoded names. 156 157 Raises: 158 ValueError: If ``env_prefix`` is empty. 159 RuntimeError: If reading/parsing the file fails. 160 """ 161 if not isinstance(self.env_prefix, str) or self.env_prefix.strip() == "": 162 raise ValueError("env_prefix must be a non-empty string") 163 164 if not self.dotenv_path: 165 return {} 166 167 path = Path(self.dotenv_path) 168 if not path.is_file(): 169 return {} 170 171 try: 172 raw_dotenv = parse_dotenv_file(str(path), encoding=self.dotenv_encoding) 173 except (OSError, UnicodeDecodeError) as exc: 174 raise RuntimeError( 175 f"Failed to read dotenv file: {self.dotenv_path}" 176 ) from exc 177 178 if not raw_dotenv: 179 return {} 180 181 prefix_upper = self.env_prefix.strip().upper() 182 if not prefix_upper.endswith("_"): 183 prefix_upper += "_" 184 prefix_len = len(prefix_upper) 185 186 filtered: dict[str, str] = {} 187 for key, value in raw_dotenv.items(): 188 key_upper = key.upper() 189 if key_upper.startswith(prefix_upper): 190 stripped = key_upper[prefix_len:] 191 if stripped: 192 filtered[stripped] = value 193 194 if not filtered: 195 return {} 196 197 if model is not None: 198 mapped, unmatched = map_env_to_model( 199 filtered, 200 model, 201 self.nested_separator, 202 collect_unmatched=True, 203 ) 204 return mapped, unmatched 205 206 return {key.lower(): value for key, value in filtered.items()}
Load configuration data from a dotenv file.
Attributes:
- dotenv_path: Dotenv file path.
- dotenv_encoding: Text encoding used to read the file.
- env_prefix: Required prefix used to filter keys (MANDATORY!).
- nested_separator: Separator used to represent nesting in keys.
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
142 def load( 143 self, 144 model: type[msgspec.Struct] | None = None, 145 ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: 146 """Read dotenv variables from file and map them to config data. 147 148 Args: 149 model: Optional target model used for key resolution and coercion. 150 151 Returns: 152 Mapping of model-recognized values. When ``model`` is ``None``, 153 returns a flat lowercase mapping. With ``model``, field resolution 154 accepts canonical and encoded names, and mapped keys are emitted as 155 encoded names. 156 157 Raises: 158 ValueError: If ``env_prefix`` is empty. 159 RuntimeError: If reading/parsing the file fails. 160 """ 161 if not isinstance(self.env_prefix, str) or self.env_prefix.strip() == "": 162 raise ValueError("env_prefix must be a non-empty string") 163 164 if not self.dotenv_path: 165 return {} 166 167 path = Path(self.dotenv_path) 168 if not path.is_file(): 169 return {} 170 171 try: 172 raw_dotenv = parse_dotenv_file(str(path), encoding=self.dotenv_encoding) 173 except (OSError, UnicodeDecodeError) as exc: 174 raise RuntimeError( 175 f"Failed to read dotenv file: {self.dotenv_path}" 176 ) from exc 177 178 if not raw_dotenv: 179 return {} 180 181 prefix_upper = self.env_prefix.strip().upper() 182 if not prefix_upper.endswith("_"): 183 prefix_upper += "_" 184 prefix_len = len(prefix_upper) 185 186 filtered: dict[str, str] = {} 187 for key, value in raw_dotenv.items(): 188 key_upper = key.upper() 189 if key_upper.startswith(prefix_upper): 190 stripped = key_upper[prefix_len:] 191 if stripped: 192 filtered[stripped] = value 193 194 if not filtered: 195 return {} 196 197 if model is not None: 198 mapped, unmatched = map_env_to_model( 199 filtered, 200 model, 201 self.nested_separator, 202 collect_unmatched=True, 203 ) 204 return mapped, unmatched 205 206 return {key.lower(): value for key, value in filtered.items()}
Read dotenv variables from file and map them to config data.
Arguments:
- model: Optional target model used for key resolution and coercion.
Returns:
Mapping of model-recognized values. When
modelisNone, returns a flat lowercase mapping. Withmodel, field resolution accepts canonical and encoded names, and mapped keys are emitted as encoded names.
Raises:
- ValueError: If
env_prefixis empty. - RuntimeError: If reading/parsing the file fails.
13class EnvironSource(DataSource): 14 """Load configuration data from process environment variables. 15 16 Attributes: 17 env_prefix: Required prefix used to select environment variables (MANDATORY!). 18 nested_separator: Separator used to represent nesting in env keys. 19 """ 20 21 env_prefix: str = "" 22 nested_separator: str = "_" 23 24 def load( 25 self, 26 model: type[msgspec.Struct] | None = None, 27 ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: 28 """Read process environment variables and map them to config data. 29 30 Args: 31 model: Optional target model used for key resolution and coercion. 32 33 Returns: 34 When ``model`` is ``None``, a flat lowercase mapping. 35 Otherwise ``(mapped, unmatched)`` where unmatched keys are captured 36 as source unmapped runtime state by the ``DataSource`` wrapper. 37 Field resolution accepts canonical and encoded names, and mapped 38 keys are emitted as encoded names. 39 40 Raises: 41 ValueError: If ``env_prefix`` is empty. 42 """ 43 if not isinstance(self.env_prefix, str) or self.env_prefix.strip() == "": 44 raise ValueError("env_prefix must be a non-empty string") 45 46 prefix_upper = self.env_prefix.strip().upper() 47 if not prefix_upper.endswith("_"): 48 prefix_upper += "_" 49 prefix_len = len(prefix_upper) 50 51 env_data: dict[str, str] = {} 52 for key, value in os.environ.items(): 53 key_upper = key.upper() 54 if key_upper.startswith(prefix_upper): 55 stripped = key_upper[prefix_len:] 56 if stripped: 57 env_data[stripped] = value 58 59 if not env_data: 60 return {} 61 62 if model is not None: 63 mapped, unmatched = map_env_to_model( 64 env_data, 65 model, 66 self.nested_separator, 67 collect_unmatched=True, 68 ) 69 return mapped, unmatched 70 71 return {key.lower(): value for key, value in env_data.items()}
Load configuration data from process environment variables.
Attributes:
- env_prefix: Required prefix used to select environment variables (MANDATORY!).
- nested_separator: Separator used to represent nesting in env keys.
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
24 def load( 25 self, 26 model: type[msgspec.Struct] | None = None, 27 ) -> dict[str, Any] | tuple[dict[str, Any], dict[str, Any]]: 28 """Read process environment variables and map them to config data. 29 30 Args: 31 model: Optional target model used for key resolution and coercion. 32 33 Returns: 34 When ``model`` is ``None``, a flat lowercase mapping. 35 Otherwise ``(mapped, unmatched)`` where unmatched keys are captured 36 as source unmapped runtime state by the ``DataSource`` wrapper. 37 Field resolution accepts canonical and encoded names, and mapped 38 keys are emitted as encoded names. 39 40 Raises: 41 ValueError: If ``env_prefix`` is empty. 42 """ 43 if not isinstance(self.env_prefix, str) or self.env_prefix.strip() == "": 44 raise ValueError("env_prefix must be a non-empty string") 45 46 prefix_upper = self.env_prefix.strip().upper() 47 if not prefix_upper.endswith("_"): 48 prefix_upper += "_" 49 prefix_len = len(prefix_upper) 50 51 env_data: dict[str, str] = {} 52 for key, value in os.environ.items(): 53 key_upper = key.upper() 54 if key_upper.startswith(prefix_upper): 55 stripped = key_upper[prefix_len:] 56 if stripped: 57 env_data[stripped] = value 58 59 if not env_data: 60 return {} 61 62 if model is not None: 63 mapped, unmatched = map_env_to_model( 64 env_data, 65 model, 66 self.nested_separator, 67 collect_unmatched=True, 68 ) 69 return mapped, unmatched 70 71 return {key.lower(): value for key, value in env_data.items()}
Read process environment variables and map them to config data.
Arguments:
- model: Optional target model used for key resolution and coercion.
Returns:
When
modelisNone, a flat lowercase mapping. Otherwise(mapped, unmatched)where unmatched keys are captured as source unmapped runtime state by theDataSourcewrapper. Field resolution accepts canonical and encoded names, and mapped keys are emitted as encoded names.
Raises:
- ValueError: If
env_prefixis empty.
13class JSONSource(DataSource): 14 """Load configuration data from inline JSON or a JSON file. 15 16 Attributes: 17 json_data: Inline JSON payload (string) to decode. 18 json_path: Optional JSON file path used when ``json_data`` is unset. 19 json_encoding: Text encoding used to read ``json_path``. 20 """ 21 22 json_data: str | None = None 23 json_path: str | None = None 24 json_encoding: str = "utf-8" 25 26 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 27 """Decode JSON configuration data. 28 29 Args: 30 model: Optional target model requesting data. Accepted for interface 31 compatibility. 32 33 Returns: 34 Parsed mapping data, or an empty mapping when both inline payload 35 and path are unset/missing. 36 37 Raises: 38 RuntimeError: If file reading or JSON parsing fails. 39 """ 40 raw_data: str 41 parse_context: str 42 43 if self.json_data is not None: 44 raw_data = self.json_data 45 parse_context = "inline JSON payload" 46 else: 47 if not self.json_path: 48 return {} 49 50 path = Path(self.json_path) 51 if not path.is_file(): 52 return {} 53 54 try: 55 raw_data = path.read_text(encoding=self.json_encoding) 56 except (OSError, UnicodeDecodeError) as exc: 57 raise RuntimeError( 58 f"Failed to read JSON file: {self.json_path}" 59 ) from exc 60 parse_context = f"JSON file: {self.json_path}" 61 62 try: 63 data: Any = msgspec.json.decode(raw_data) 64 except Exception as exc: 65 raise RuntimeError(f"Failed to parse {parse_context}") from exc 66 67 if not isinstance(data, Mapping): 68 return {} 69 return data
Load configuration data from inline JSON or a JSON file.
Attributes:
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
26 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 27 """Decode JSON configuration data. 28 29 Args: 30 model: Optional target model requesting data. Accepted for interface 31 compatibility. 32 33 Returns: 34 Parsed mapping data, or an empty mapping when both inline payload 35 and path are unset/missing. 36 37 Raises: 38 RuntimeError: If file reading or JSON parsing fails. 39 """ 40 raw_data: str 41 parse_context: str 42 43 if self.json_data is not None: 44 raw_data = self.json_data 45 parse_context = "inline JSON payload" 46 else: 47 if not self.json_path: 48 return {} 49 50 path = Path(self.json_path) 51 if not path.is_file(): 52 return {} 53 54 try: 55 raw_data = path.read_text(encoding=self.json_encoding) 56 except (OSError, UnicodeDecodeError) as exc: 57 raise RuntimeError( 58 f"Failed to read JSON file: {self.json_path}" 59 ) from exc 60 parse_context = f"JSON file: {self.json_path}" 61 62 try: 63 data: Any = msgspec.json.decode(raw_data) 64 except Exception as exc: 65 raise RuntimeError(f"Failed to parse {parse_context}") from exc 66 67 if not isinstance(data, Mapping): 68 return {} 69 return data
Decode JSON configuration data.
Arguments:
- model: Optional target model requesting data. Accepted for interface compatibility.
Returns:
Parsed mapping data, or an empty mapping when both inline payload and path are unset/missing.
Raises:
- RuntimeError: If file reading or JSON parsing fails.
13class TomlSource(DataSource): 14 """Load configuration data from a TOML file. 15 16 Attributes: 17 toml_path: Path to the TOML file to load. 18 toml_encoding: Text encoding used to read the file. 19 """ 20 21 toml_path: str | None = None 22 toml_encoding: str = "utf-8" 23 24 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 25 """Read and parse TOML configuration data. 26 27 Args: 28 model: Optional target model requesting data. Accepted for interface 29 compatibility. 30 31 Returns: 32 Parsed mapping data. Returns an empty mapping when path is 33 unset/missing. 34 35 Raises: 36 RuntimeError: If file reading or TOML parsing fails. 37 """ 38 if not self.toml_path: 39 return {} 40 41 path = Path(self.toml_path) 42 if not path.is_file(): 43 return {} 44 45 try: 46 raw_data = path.read_text(encoding=self.toml_encoding) 47 except (OSError, UnicodeDecodeError) as exc: 48 raise RuntimeError(f"Failed to read TOML file: {self.toml_path}") from exc 49 50 try: 51 data: Any = msgspec.toml.decode(raw_data) 52 except Exception as exc: 53 raise RuntimeError(f"Failed to parse TOML file: {self.toml_path}") from exc 54 55 if not isinstance(data, Mapping): 56 return {} 57 58 return data
Load configuration data from a TOML file.
Attributes:
- toml_path: Path to the TOML file to load.
- toml_encoding: Text encoding used to read the file.
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
24 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 25 """Read and parse TOML configuration data. 26 27 Args: 28 model: Optional target model requesting data. Accepted for interface 29 compatibility. 30 31 Returns: 32 Parsed mapping data. Returns an empty mapping when path is 33 unset/missing. 34 35 Raises: 36 RuntimeError: If file reading or TOML parsing fails. 37 """ 38 if not self.toml_path: 39 return {} 40 41 path = Path(self.toml_path) 42 if not path.is_file(): 43 return {} 44 45 try: 46 raw_data = path.read_text(encoding=self.toml_encoding) 47 except (OSError, UnicodeDecodeError) as exc: 48 raise RuntimeError(f"Failed to read TOML file: {self.toml_path}") from exc 49 50 try: 51 data: Any = msgspec.toml.decode(raw_data) 52 except Exception as exc: 53 raise RuntimeError(f"Failed to parse TOML file: {self.toml_path}") from exc 54 55 if not isinstance(data, Mapping): 56 return {} 57 58 return data
Read and parse TOML configuration data.
Arguments:
- model: Optional target model requesting data. Accepted for interface compatibility.
Returns:
Parsed mapping data. Returns an empty mapping when path is unset/missing.
Raises:
- RuntimeError: If file reading or TOML parsing fails.
13class YamlSource(DataSource): 14 """Load configuration data from a YAML file. 15 16 Attributes: 17 yaml_path: Path to the YAML file to load. 18 yaml_encoding: Text encoding used to read the file. 19 """ 20 21 yaml_path: str | None = None 22 yaml_encoding: str = "utf-8" 23 24 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 25 """Read and parse YAML configuration data. 26 27 Args: 28 model: Optional target model requesting data. Accepted for interface 29 compatibility. 30 31 Returns: 32 Parsed mapping data. Returns an empty mapping when path is 33 unset/missing. 34 35 Raises: 36 RuntimeError: If file reading or YAML parsing fails. 37 """ 38 if not self.yaml_path: 39 return {} 40 41 path = Path(self.yaml_path) 42 if not path.is_file(): 43 return {} 44 45 try: 46 raw_data = path.read_text(encoding=self.yaml_encoding) 47 except (OSError, UnicodeDecodeError) as exc: 48 raise RuntimeError(f"Failed to read YAML file: {self.yaml_path}") from exc 49 50 try: 51 data: Any = msgspec.yaml.decode(raw_data) 52 except Exception as exc: 53 raise RuntimeError(f"Failed to parse YAML file: {self.yaml_path}") from exc 54 55 if not isinstance(data, Mapping): 56 return {} 57 return data
Load configuration data from a YAML file.
Attributes:
- yaml_path: Path to the YAML file to load.
- yaml_encoding: Text encoding used to read the file.
100 def __call__(cls: type["DataModel"], *args: Any, **kwargs: Any) -> "DataModel": 101 """Instantiate and validate a ``DataModel`` instance. 102 103 For regular models, configured datasource definitions are cloned, 104 loaded, and merged before final conversion. For ``DataSource`` 105 subclasses, this method behaves like normal struct instantiation. 106 107 Args: 108 *args: Positional arguments. Not supported. 109 **kwargs: Field values and explicit overrides. Unknown keys are 110 captured on the instance and exposed via 111 :meth:`DataModel.get_unmapped_payload`. 112 113 Returns: 114 Validated model instance with runtime datasource state attached, 115 including unmapped payload tracking and raw argv fragments produced 116 by sources. 117 118 Raises: 119 TypeError: If positional arguments are provided. 120 """ 121 if args: 122 raise TypeError( 123 f"Class {cls.__name__} does not support positional arguments. " 124 "Use keyword arguments instead." 125 ) 126 127 prepared_kwargs, constructor_unmapped = cls._split_constructor_kwargs(kwargs) 128 datasource_instances: tuple["DataSource", ...] = () 129 unmapped_cache: dict[str, Any] | None = {} 130 131 if cls.__datasource_defs__ is not None and not issubclass(cls, DataSource): 132 prepared, datasource_instances = cls._collect_datasources_payload( 133 *cls.__datasource_defs__, **prepared_kwargs 134 ) 135 unmapped_cache = None 136 else: 137 prepared = prepared_kwargs 138 unmapped_cache = constructor_unmapped 139 instance = msgspec.convert(prepared, type=cls) 140 return cls._setup_instance( 141 instance, 142 datasource_instances=datasource_instances, 143 unmapped_cache=unmapped_cache, 144 constructor_unmapped=constructor_unmapped, 145 )
Instantiate and validate a DataModel instance.
For regular models, configured datasource definitions are cloned,
loaded, and merged before final conversion. For DataSource
subclasses, this method behaves like normal struct instantiation.
Arguments:
- *args: Positional arguments. Not supported.
- **kwargs: Field values and explicit overrides. Unknown keys are
captured on the instance and exposed via
DataModel.get_unmapped_payload().
Returns:
Validated model instance with runtime datasource state attached, including unmapped payload tracking and raw argv fragments produced by sources.
Raises:
- TypeError: If positional arguments are provided.
24 def load(self, model: type[msgspec.Struct] | None = None) -> Mapping[str, Any]: 25 """Read and parse YAML configuration data. 26 27 Args: 28 model: Optional target model requesting data. Accepted for interface 29 compatibility. 30 31 Returns: 32 Parsed mapping data. Returns an empty mapping when path is 33 unset/missing. 34 35 Raises: 36 RuntimeError: If file reading or YAML parsing fails. 37 """ 38 if not self.yaml_path: 39 return {} 40 41 path = Path(self.yaml_path) 42 if not path.is_file(): 43 return {} 44 45 try: 46 raw_data = path.read_text(encoding=self.yaml_encoding) 47 except (OSError, UnicodeDecodeError) as exc: 48 raise RuntimeError(f"Failed to read YAML file: {self.yaml_path}") from exc 49 50 try: 51 data: Any = msgspec.yaml.decode(raw_data) 52 except Exception as exc: 53 raise RuntimeError(f"Failed to parse YAML file: {self.yaml_path}") from exc 54 55 if not isinstance(data, Mapping): 56 return {} 57 return data
Read and parse YAML configuration data.
Arguments:
- model: Optional target model requesting data. Accepted for interface compatibility.
Returns:
Parsed mapping data. Returns an empty mapping when path is unset/missing.
Raises:
- RuntimeError: If file reading or YAML parsing fails.