config
napt.config
Configuration loading and management for NAPT.
Loads, merges, and validates YAML-based configuration files with a layered approach:
- Organization-wide defaults (defaults/org.yaml)
- Vendor-specific defaults (defaults/vendors/{Vendor}.yaml)
- Recipe-specific configuration (recipes/{Vendor}/{app}.yaml)
The loader performs deep merging where dicts are merged recursively and lists/scalars are replaced (last wins). Relative paths are resolved against the recipe file location for relocatability.
Modules:
| Name | Description |
|---|---|
loader |
The 3-layer configuration loader (load_effective_config). |
defaults |
Built-in default configuration and the org.yaml template. |
napt.config.loader
Configuration loading and merging for NAPT.
This module implements a layered configuration system that allows NAPT to work out of the box while supporting full customization. Each layer wins over the previous, promoting DRY (Don't Repeat Yourself) principles.
Configuration Layers
- Code defaults (napt/config/defaults.py)
- Built-in defaults that ship with NAPT
- Always present; ensures NAPT works without any config files
-
Provides sensible defaults for all settings
-
Organization defaults (defaults/org.yaml)
- Organization-wide settings
- Optional; only loaded if file exists
-
Customizes settings for your organization
-
Vendor defaults (defaults/vendors/{Vendor}.yaml)
- Vendor-specific settings (e.g., Google-specific settings)
- Optional; only loaded if vendor is detected
-
Wins over organization defaults
-
Parent recipe (the file named by the recipe's
parentfield) - Another recipe merged beneath this one
- Optional; a parent may not itself declare a parent
-
Wins over vendor defaults
-
Recipe configuration (recipes/{Vendor}/{app}.yaml)
- App-specific configuration
- Always required; defines the app itself
- Wins over all other layers
Merge Behavior
The loader performs deep merging with "last wins" semantics:
- Dicts: Recursively merged (keys from overlay win over base)
- Lists: Completely replaced (NOT appended/extended)
- Scalars: Overwritten (strings, numbers, booleans)
Path Resolution
Relative paths in configuration are resolved against the RECIPE FILE location, making recipes relocatable and portable. A parent's relative paths resolve against the child recipe, not the parent file. Currently resolved paths:
- psadt.brand_pack.path
- intune.logo_path
Dynamic Injection
Some fields are injected at load time:
- psadt.app_vars.AppScriptDate: Today's date (YYYY-MM-DD)
Error Handling
- ConfigError: Recipe file doesn't exist, YAML parse errors, empty files, invalid structure, a missing parent, or a parent chain
- All errors are chained with "from err" for better debugging
Note
- Code defaults are always applied first (NAPT works without config files)
- The loader walks upward from the recipe to find defaults/org.yaml
- Organization and vendor defaults are optional layers
- Vendor is detected from directory name (recipes/Google/) or recipe content
- Paths are resolved relative to the recipe, not the working directory
- Dynamic fields are best-effort (warnings on failure, not errors)
load_parent
Loads the parent recipe a recipe declares, if any.
The parent field names another recipe file relative to the
declaring recipe's directory. The parent is merged beneath the
declaring recipe by
load_effective_config and by
napt validate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recipe_path
|
Path
|
Path to the recipe that may declare |
required |
recipe_obj
|
dict[str, Any]
|
The parsed recipe dictionary. |
required |
Returns:
| Type | Description |
|---|---|
tuple[Path, dict[str, Any]] | None
|
The resolved parent path and its parsed contents, or None when the recipe declares no parent. |
Raises:
| Type | Description |
|---|---|
ConfigError
|
When |
Source code in napt/config/loader.py
merge_parent
Merges a recipe over its parent without the other configuration layers.
Used by napt validate, which checks a recipe's own schema rather than
the fully merged configuration. The recipe's parent field survives
the merge so schema validation can see it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recipe_path
|
Path
|
Path to the recipe that may declare |
required |
recipe_obj
|
dict[str, Any]
|
The parsed recipe dictionary. |
required |
Returns:
| Type | Description |
|---|---|
tuple[dict[str, Any], Path | None]
|
The merged dictionary and the parent path, or the recipe unchanged and None when it declares no parent. |
Raises:
| Type | Description |
|---|---|
ConfigError
|
See load_parent. |
Source code in napt/config/loader.py
load_effective_config
Loads and merges the effective configuration for a recipe.
Performs the following operations:
- Read recipe YAML and its parent recipe, if it declares one
- Find defaults root by scanning upwards for defaults/org.yaml
- Load org defaults (required if defaults root exists)
- Determine vendor (param vendor > folder name > recipe contents)
- Load vendor defaults if present
- Merge: org -> vendor -> parent -> recipe (dicts deep-merge, lists replace)
- Resolve known relative paths (relative to the recipe directory)
- Inject dynamic fields (AppScriptDate = today if absent)
The returned dict does not carry the parent field; the parent's
contents are already merged in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recipe_path
|
Path
|
Path to the recipe YAML file. |
required |
vendor
|
str | None
|
Optional vendor name. If not provided, vendor is detected from the folder name or recipe contents. |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A merged configuration dict ready for downstream processors. If no defaults were found in the tree, the recipe is returned as-is (with path resolution and injection). |
Raises:
| Type | Description |
|---|---|
ConfigError
|
On YAML parse errors, empty files, invalid structure, a missing recipe or parent file, or a parent chain. |
Source code in napt/config/loader.py
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 | |
napt.config.defaults
Default configuration values for NAPT.
This module provides the baseline configuration that ships with NAPT. These defaults are always applied first, then overridden by organization defaults (org.yaml), vendor defaults, and finally recipe-specific settings.
The configuration hierarchy is
- Code defaults (this module) - always present
- Organization defaults (defaults/org.yaml) - optional overrides
- Vendor defaults (defaults/vendors/{Vendor}.yaml) - optional overrides
- Recipe configuration - required, app-specific settings
This design ensures that NAPT works out of the box without requiring any configuration files, while still allowing full customization when needed.
Note
Authentication for 'napt upload' requires no config file. Developers run 'napt auth login' once; CI/CD pipelines set AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_CLIENT_SECRET, or use OIDC federation.