implemented utility function for merging dictionaries

This commit is contained in:
Linus Vogel 2026-02-15 11:13:22 +01:00
parent f47d3eb0a8
commit ace0062f37
3 changed files with 100 additions and 1 deletions

View File

@ -0,0 +1,43 @@
import uuid
from fastapi.params import Depends
from sqlalchemy import select, insert, delete
from sqlalchemy.orm import Session
from starlette.exceptions import HTTPException
from starlette.requests import Request
from fastapi import APIRouter
from starlette.responses import JSONResponse
from pillar_tool.db.models.top_data import State, StateAssignment
from pillar_tool.schemas import PillarParams, get_model_from_query
from pillar_tool.util.validation import validate_state_name
router = APIRouter(
prefix="/pillar",
tags=["pillar"],
)
# Note: there is no list of all pillars, as this would not be helpful
@router.get("/{name}")
def state_get(req: Request, name: str, params: Depends(get_model_from_query(PillarParams))):
# TODO: implement
# this function should:
# - get the affected host hierarchy
# - get all the relevant pillar dictionaries
# - merge the pillar directories
# - return the merged pillar directory
# if any error happens, return non-200 status and an empty dictionary so that salt does not shit itself
db: Session = req.state.db
@router.post("/{name}")
def state_create(req: Request, name: str):
# TODO: implement
db = req.state.db
@router.delete("/{name}")
def state_delete(req: Request, name: str):
# TODO: implement
db = req.state.db

View File

@ -50,7 +50,11 @@ class HostgroupParams(BaseModel):
class StateParams(BaseModel):
pass # No parameters needed for state operations currently
# Pillar operations
class PillarParams(BaseModel):
target: str # must be host or hostgroup
value: str | None # value if the pillar should be set
type: str | None # type of pillar if pillar should be set
def get_model_from_query[T](model: T) -> Callable[[Request], T]:

View File

@ -0,0 +1,52 @@
from copy import deepcopy
def apply_layer(base: dict, layer: dict):
"""
Recursively applies key-value pairs from `layer` onto `base`.
For each key in `layer`:
- If both `base[key]` and `layer[key]` are dictionaries, recursively merge them.
- Otherwise, `base[key]` is overwritten (or newly inserted) with `layer[key]`.
Note: This function mutates the `base` dictionary in-place.
:param base: The target dictionary to be updated. Will be modified directly.
:param layer: The source dictionary whose values will be applied to `base`.
"""
for key, value in layer.items():
# if base and layer value are dicts, apply recursively
if type(value) is dict and key in base and type(base[key]) is dict:
apply_layer(base[key], value)
# else replace the base value with the layer value
# or insert the base value
else:
base[key] = value
def merge(*pillar_data, deep_copy=True) -> dict:
"""
Merges multiple pillar data dictionaries into one.
The merging is done left-to-right: keys from later dictionaries override
those in earlier ones. Nested dictionaries are merged recursively using
`apply_layer`.
:param pillar_data: Two or more dictionaries to merge. Must contain at least one item.
:param deep_copy: If True (default), the first dictionary is deep-copied before merging,
preserving the original input data. If False, the first dictionary
is modified in-place.
:return: A new merged dictionary (if `deep_copy=True`) or the mutated first dictionary (if `deep_copy=False`).
Example:
merge({'a': 1}, {'b': 2}) {'a': 1, 'b': 2}
merge({'a': {'x': 1}}, {'a': {'y': 2}}) {'a': {'x': 1, 'y': 2}}
"""
assert len(pillar_data) > 0, "At least one pillar data is required"
merged_pillar = deepcopy(pillar_data[0]) if deep_copy else pillar_data[0]
for pillar in pillar_data[1:]:
apply_layer(merged_pillar, pillar)
return merged_pillar