fix(marker): fix inheritance hook, topology load, update sync, and asdict

Bugs found via end-to-end testing of project-level marker definitions:

1. New links didn't inherit — apply_defs_to_new_link is async but was
   called without await in UDPLink.create().

2. Project load crashed — load_project passed marker_definitions to
   Project.__init__. Now popped in load_project and restored separately
   in Project.open() (it backs a read-only property).

3. PUT on a definition didn't sync to links — update_marker's guard
   rejected even the project-layer sync call. Added an `inherited`
   bypass flag used by update_marker_definition.

4. _ubridge_add_marker_filter raised re.PatternError — the name regex
   used (?i)(?!global) look-around, invalid in Python's re module.
   Dropped the prefix check there: "global-*" names are legitimate at
   the uBridge boundary (inherited definitions); forbidden only at the
   user-facing schema.

5. GET /links hid inherited markers — asdict()'s runtime branch used
   _persist_markers() (which filters inherited markers). Restored
   self._markers for the runtime branch; only the topology_dump branch
   filters (inherited markers are rebuilt from definitions on load).

6. Duplicate "already exists" warnings on project open — open() fanned
   out definitions to all links, but UDPLink.create() had already done
   so via its inheritance hook. Removed the redundant fan-out in open().
This commit is contained in:
YueGuobin committed 2026-07-16 00:39:52 +08:00
1 parent 84845f8504
commit 179f072c33
5 files changed
+23 -11

No files matched your search

+10 -6
View File
@@ -970,7 +970,7 @@ class Project:
marker_name = f"global-{name}"
if marker_name in link.markers and link.markers[marker_name].get("inherited_from") == name:
await link.update_marker(
marker_name, bpf=d["bpf"], tag=d.get("tag"), color=d.get("color")
marker_name, bpf=d["bpf"], tag=d.get("tag"), color=d.get("color"), inherited=True
)
self.dump()
self.emit_notification("project.updated", self.asdict())
@@ -1410,7 +1410,6 @@ class Project:
"auto_start",
"auto_close",
"auto_open",
"marker_definitions",
"scene_height",
"scene_width",
"zoom",
@@ -1427,6 +1426,12 @@ class Project:
if val is not None:
setattr(self, key, val)
# marker_definitions is loaded separately (it is not a __init__ kwarg
# nor a simple attribute — it backs a read-only property).
defs = project_data.get("marker_definitions")
if isinstance(defs, dict):
self._marker_definitions = defs
topology = project_data["topology"]
for compute in topology.get("computes", []):
compute_id = compute.get("compute_id")
@@ -1493,10 +1498,9 @@ class Project:
for drawing_data in topology.get("drawings", []):
await self.add_drawing(dump=False, **drawing_data)
# After every link is loaded, apply project-level marker definitions
# so inherited markers are present from the start.
for link in list(self._links.values()):
await self.apply_defs_to_new_link(link)
# Note: project-level marker definitions are applied to each link
# inside UDPLink.create() (the inheritance hook), so they are
# already present once links are loaded — no separate fan-out here.
self.dump()
# We catch all error to be able to roll back the .gns3 to the previous state