From 342607473ad4afcee7e199de8ee2c133c23b73b9 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Thu, 10 Apr 2025 14:55:51 -0500 Subject: [PATCH] Make Endpoint::respec_deep() infinitely deep (#11527) Because it wasn't recursive, there was a limit to the depth of updates. This work is necessary because as we teach neon_local and compute_ctl that the content in --spec-path should match a similar structure we get from the control plane, the spec object itself will no longer be toplevel. It will be under the "spec" key. Signed-off-by: Tristan Partin --- test_runner/fixtures/neon_fixtures.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test_runner/fixtures/neon_fixtures.py b/test_runner/fixtures/neon_fixtures.py index ba8de1c01c..858d367abf 100644 --- a/test_runner/fixtures/neon_fixtures.py +++ b/test_runner/fixtures/neon_fixtures.py @@ -14,6 +14,7 @@ import threading import time import uuid from collections import defaultdict +from collections.abc import Mapping from contextlib import closing, contextmanager from dataclasses import dataclass from datetime import datetime @@ -4296,28 +4297,29 @@ class Endpoint(PgProtocol, LogUtils): def respec_deep(self, **kwargs: Any) -> None: """ - Update the endpoint.json file taking into account nested keys. - It does one level deep update. Should enough for most cases. - Distinct method from respec() to do not break existing functionality. + Update the spec.json file taking into account nested keys. + Distinct method from respec() to not break existing functionality. NOTE: This method also updates the spec.json file, not endpoint.json. We need it because neon_local also writes to spec.json, so intended use-case is i) start endpoint with some config, ii) respec_deep(), iii) call reconfigure() to apply the changes. """ + + def update(curr, patch): + for k, v in patch.items(): + if isinstance(v, Mapping): + curr[k] = update(curr.get(k, {}), v) + else: + curr[k] = v + return curr + config_path = os.path.join(self.endpoint_path(), "spec.json") with open(config_path) as f: data_dict: dict[str, Any] = json.load(f) log.debug("Current compute spec: %s", json.dumps(data_dict, indent=4)) - for key, value in kwargs.items(): - if isinstance(value, dict): - if key not in data_dict: - data_dict[key] = value - else: - data_dict[key] = {**data_dict[key], **value} - else: - data_dict[key] = value + update(data_dict, kwargs) with open(config_path, "w") as file: log.debug("Updating compute spec to: %s", json.dumps(data_dict, indent=4))