Move shared config params into template

This commit is contained in:
tian 2026-04-20 12:27:39 +08:00
parent 502991d026
commit cd6d299bd3
4 changed files with 91 additions and 10 deletions

View File

@ -17,14 +17,7 @@
"publish_hls_path": "./web/hls/cam1/index.m3u8", "publish_hls_path": "./web/hls/cam1/index.m3u8",
"publish_rtsp_port": 8555, "publish_rtsp_port": 8555,
"publish_rtsp_path": "/live/cam1", "publish_rtsp_path": "/live/cam1",
"channel_no": "cam1", "channel_no": "cam1"
"minio_endpoint": "http://10.0.0.49:9000",
"minio_bucket": "myminio",
"minio_access_key": "admin",
"minio_secret_key": "password",
"external_get_token_url": "http://10.0.0.49:8080/api/getToken",
"external_put_message_url": "http://10.0.0.49:8080/api/putMessage",
"tenant_code": "32"
} }
} }
] ]

View File

@ -2,6 +2,15 @@
"name": "workshop_face_shoe_alarm", "name": "workshop_face_shoe_alarm",
"description": "Full 1080p workshop pipeline with face detection/recognition, person tracking, shoe detection, OSD, publishing, alarms, snapshots, clips, and external API upload.", "description": "Full 1080p workshop pipeline with face detection/recognition, person tracking, shoe detection, OSD, publishing, alarms, snapshots, clips, and external API upload.",
"source": "configs/full_pipeline_1080p_test_alarm.json", "source": "configs/full_pipeline_1080p_test_alarm.json",
"params": {
"minio_endpoint": "http://10.0.0.49:9000",
"minio_bucket": "myminio",
"minio_access_key": "admin",
"minio_secret_key": "password",
"external_get_token_url": "http://10.0.0.49:8080/api/getToken",
"external_put_message_url": "http://10.0.0.49:8080/api/putMessage",
"tenant_code": "32"
},
"template": { "template": {
"executor": { "executor": {
"batch_size": 2, "batch_size": 2,

View File

@ -39,6 +39,14 @@ class RenderConfigTest(unittest.TestCase):
self.assertIn("publish_rtsp_port", params) self.assertIn("publish_rtsp_port", params)
self.assertIn("publish_rtsp_path", params) self.assertIn("publish_rtsp_path", params)
self.assertIn("channel_no", params) self.assertIn("channel_no", params)
self.assertNotIn("minio_endpoint", params)
self.assertNotIn("external_get_token_url", params)
self.assertNotIn("tenant_code", params)
shared_params = template["params"]
self.assertEqual(shared_params["minio_endpoint"], "http://10.0.0.49:9000")
self.assertEqual(shared_params["external_get_token_url"], "http://10.0.0.49:8080/api/getToken")
self.assertEqual(shared_params["tenant_code"], "32")
def test_render_profile_no_longer_requires_face_gallery_path(self): def test_render_profile_no_longer_requires_face_gallery_path(self):
profile_path = REPO_ROOT / "configs" / "profiles" / "local_3588_test.json" profile_path = REPO_ROOT / "configs" / "profiles" / "local_3588_test.json"
@ -62,6 +70,7 @@ class RenderConfigTest(unittest.TestCase):
module = load_module() module = load_module()
template = { template = {
"name": "pipeline", "name": "pipeline",
"params": {"tenant_code": "32"},
"template": { "template": {
"executor": {"batch_size": 2}, "executor": {"batch_size": 2},
"nodes": [ "nodes": [
@ -94,7 +103,7 @@ class RenderConfigTest(unittest.TestCase):
root = { root = {
"templates": {module.template_name(template, pathlib.Path("x.json")): module.template_body(template)}, "templates": {module.template_name(template, pathlib.Path("x.json")): module.template_body(template)},
"instances": module.profile_instances(profile, "pipeline"), "instances": module.merge_template_params(module.profile_instances(profile, "pipeline"), module.template_params(template)),
"queue": profile["queue"], "queue": profile["queue"],
} }
rendered = module.apply_overlay(root, overlay) rendered = module.apply_overlay(root, overlay)
@ -102,10 +111,55 @@ class RenderConfigTest(unittest.TestCase):
self.assertEqual(rendered["queue"]["size"], 8) self.assertEqual(rendered["queue"]["size"], 8)
self.assertEqual(rendered["instances"][0]["template"], "pipeline") self.assertEqual(rendered["instances"][0]["template"], "pipeline")
self.assertEqual(rendered["instances"][0]["params"]["rtsp_url"], "rtsp://example/cam1") self.assertEqual(rendered["instances"][0]["params"]["rtsp_url"], "rtsp://example/cam1")
self.assertEqual(rendered["instances"][0]["params"]["tenant_code"], "32")
node_override = rendered["instances"][0]["override"]["nodes"]["face_recog"] node_override = rendered["instances"][0]["override"]["nodes"]["face_recog"]
self.assertTrue(node_override["debug"]["enabled"]) self.assertTrue(node_override["debug"]["enabled"])
self.assertTrue(node_override["debug"]["log_matches"]) self.assertTrue(node_override["debug"]["log_matches"])
def test_template_params_fill_placeholders_without_staying_in_profile(self):
module = load_module()
with tempfile.TemporaryDirectory() as tmp_dir:
tmp = pathlib.Path(tmp_dir)
template_path = tmp / "template.json"
profile_path = tmp / "profile.json"
template_path.write_text(
"""
{
"name": "pipeline",
"params": {
"minio_endpoint": "http://10.0.0.49:9000",
"tenant_code": "32"
},
"template": {
"nodes": [{"id": "alarm", "type": "alarm", "endpoint": "${minio_endpoint}", "tenant": "${tenant_code}"}],
"edges": []
}
}
""",
encoding="utf-8",
)
profile_path.write_text(
"""
{
"name": "local_3588_test",
"instances": [
{
"name": "cam1",
"params": {"rtsp_url": "rtsp://example/cam1"}
}
]
}
""",
encoding="utf-8",
)
rendered = module.render(template_path, profile_path, [])
params = rendered["instances"][0]["params"]
self.assertEqual(params["minio_endpoint"], "http://10.0.0.49:9000")
self.assertEqual(params["tenant_code"], "32")
self.assertEqual(params["rtsp_url"], "rtsp://example/cam1")
def test_render_applies_multiple_overlays_in_order(self): def test_render_applies_multiple_overlays_in_order(self):
module = load_module() module = load_module()
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:

View File

@ -50,6 +50,15 @@ def template_body(template_doc: JsonObject) -> JsonObject:
return {key: copy.deepcopy(value) for key, value in body.items() if key in allowed} return {key: copy.deepcopy(value) for key, value in body.items() if key in allowed}
def template_params(template_doc: JsonObject) -> JsonObject:
params = template_doc.get("params", {})
if params is None:
return {}
if not isinstance(params, dict):
raise ValueError("template params must be a JSON object")
return copy.deepcopy(params)
def profile_instances(profile: JsonObject, tpl_name: str) -> list[JsonObject]: def profile_instances(profile: JsonObject, tpl_name: str) -> list[JsonObject]:
if "instances" in profile: if "instances" in profile:
instances = profile["instances"] instances = profile["instances"]
@ -77,6 +86,22 @@ def profile_instances(profile: JsonObject, tpl_name: str) -> list[JsonObject]:
] ]
def merge_template_params(instances: list[JsonObject], shared_params: JsonObject) -> list[JsonObject]:
if not shared_params:
return instances
out: list[JsonObject] = []
for item in instances:
inst = copy.deepcopy(item)
params = inst.get("params", {})
if params is None:
params = {}
if not isinstance(params, dict):
raise ValueError("instance params must be a JSON object")
inst["params"] = deep_merge(shared_params, params)
out.append(inst)
return out
def merge_instance_patch(instance: JsonObject, patch: JsonObject) -> JsonObject: def merge_instance_patch(instance: JsonObject, patch: JsonObject) -> JsonObject:
merged = copy.deepcopy(instance) merged = copy.deepcopy(instance)
if "params" in patch: if "params" in patch:
@ -137,7 +162,7 @@ def render(
root: JsonObject = { root: JsonObject = {
"templates": {tpl_name: template_body(template_doc)}, "templates": {tpl_name: template_body(template_doc)},
"instances": profile_instances(profile, tpl_name), "instances": merge_template_params(profile_instances(profile, tpl_name), template_params(template_doc)),
} }
for key in ("global", "queue"): for key in ("global", "queue"):
if key in profile: if key in profile: