Coverage for netbox_interface_name_rules / branching.py: 100.00%
47 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-16 10:13 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-10-16 10:13 +0000
1# SPDX-License-Identifier: Apache-2.0
2# Copyright (C) 2025 Marcin Zieba <marcinpsk@gmail.com>
3"""The one module that uses netbox-branching. It imports netbox-branching only in a function that needs it."""
5import functools
6from contextvars import ContextVar
8from django.apps import apps
9from django.core.exceptions import ImproperlyConfigured
10from packaging.version import Version
12APP_LABEL = "netbox_branching"
13SUPPORTED_RELEASE = (1, 2)
14SUPPORTED_SERIES = ".".join(map(str, SUPPORTED_RELEASE)) + ".x"
15# The methods of netbox-branching's Branch that replay logged changes.
16REPLAYING_METHODS = ("merge", "revert", "sync")
17# The attribute that marks a method this module wrapped.
18REPLAY_MARK = "marks_a_replay"
20_replaying = ContextVar("netbox_interface_name_rules_replay", default=False)
23def check_version(version: str) -> None:
24 """Raise ``ImproperlyConfigured`` unless *version* is a release of the ``SUPPORTED_SERIES``."""
25 if Version(version).release[:2] != SUPPORTED_RELEASE:
26 raise ImproperlyConfigured(
27 f"Interface Name Rules supports netbox-branching {SUPPORTED_SERIES} only. The installed version is {version}."
28 )
31def installed_version() -> str:
32 """Return the version of the installed netbox-branching."""
33 return apps.get_app_config(APP_LABEL).version
36def ready() -> None:
37 """When netbox-branching is installed, check its version and mark each of its replays in the context that runs it."""
38 if not apps.is_installed(APP_LABEL):
39 return
40 check_version(installed_version())
41 from netbox_branching.models import Branch
43 for name in REPLAYING_METHODS:
44 method = getattr(Branch, name)
45 if not getattr(method, REPLAY_MARK, False):
46 setattr(Branch, name, _marking_a_replay(method))
49def _marking_a_replay(method):
50 """Return *method*, wrapped so that the context that runs it is in a replay until it returns or raises."""
52 @functools.wraps(method)
53 def replay(*args, **kwargs):
54 token = _replaying.set(True)
55 try:
56 return method(*args, **kwargs)
57 finally:
58 _replaying.reset(token)
60 setattr(replay, REPLAY_MARK, True)
61 return replay
64def replay_in_progress() -> bool:
65 """Return whether a netbox-branching merge, revert or sync runs in this context."""
66 return _replaying.get()
69def branch_identity() -> str | None:
70 """Return the schema ID of the active branch, or None on main and when netbox-branching is not installed."""
71 if not apps.is_installed(APP_LABEL):
72 return None
73 from netbox_branching.contextvars import active_branch
75 branch = active_branch.get()
76 return None if branch is None else branch.schema_id
79def activate_on(request, schema_id: str | None) -> None:
80 """Put netbox-branching's branch cookie for *schema_id* on *request*; without either, the request stays on main."""
81 if schema_id is None or not apps.is_installed(APP_LABEL):
82 return
83 from netbox_branching.constants import COOKIE_NAME
85 request.COOKIES[COOKIE_NAME] = schema_id