Skip to content

Commit 82e0227

Browse files
authored
670 - Add extra branching support to pynetbox (#686)
1 parent ff0fd63 commit 82e0227

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

docs/branching.rst

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
Branching Plugin
2+
================
3+
4+
The NetBox branching plugin allows you to create and work with branches in NetBox, similar to version control systems. This enables you to make changes in isolation and merge them back to the main branch when ready.
5+
6+
Activating Branches
7+
-----------------
8+
9+
The `activate_branch` context manager allows you to perform operations within a specific branch's schema. All operations performed within the context manager will use that branch's schema.
10+
11+
.. code-block:: python
12+
13+
import pynetbox
14+
15+
# Initialize the API
16+
nb = pynetbox.api(
17+
"http://localhost:8000",
18+
token="your-token-here"
19+
)
20+
21+
# Get an existing branch
22+
branch = nb.plugins.branching.branches.get(id=1)
23+
24+
# Activate the branch for operations
25+
with nb.activate_branch(branch):
26+
# All operations within this block will use the branch's schema
27+
sites = nb.dcim.sites.all()
28+
# Make changes to objects...
29+
# These changes will only exist in this branch
30+
31+
Waiting for Branch Status
32+
-----------------------
33+
34+
When working with branches, you often need to wait for certain status changes, such as when a branch becomes ready after creation or when a merge operation completes. The `tenacity`_ library provides a robust way to handle these waiting scenarios.
35+
36+
First, install tenacity:
37+
38+
.. code-block:: bash
39+
40+
pip install tenacity
41+
42+
Here's how to create a reusable function to wait for branch status changes:
43+
44+
.. code-block:: python
45+
46+
from tenacity import retry, retry_if_result, stop_after_attempt, wait_exponential
47+
import pynetbox
48+
49+
@retry(
50+
stop=stop_after_attempt(30), # Try for up to 30 attempts
51+
wait=wait_exponential(
52+
multiplier=1, min=4, max=60
53+
), # Wait between 4-60 seconds, increasing exponentially
54+
retry=retry_if_result(lambda x: not x), # Retry if the status check returns False
55+
)
56+
def wait_for_branch_status(branch, target_status):
57+
"""Wait for branch to reach a specific status, with exponential backoff."""
58+
branch = nb.plugins.branching.branches.get(branch.id)
59+
return str(branch.status) == target_status
60+
61+
# Example usage:
62+
branch = nb.plugins.branching.branches.create(name="my-branch")
63+
64+
# Wait for branch to be ready
65+
wait_for_branch_status(branch, "Ready")
66+
67+
# Get the latest branch status
68+
branch = nb.plugins.branching.branches.get(branch.id)
69+
print(f"Branch is now ready! Status: {branch.status}")
70+
71+
The function will:
72+
1. Check the current status of the branch
73+
2. If the status doesn't match the target status, it will retry with exponential backoff
74+
3. Continue retrying until either:
75+
- The branch reaches the target status
76+
- The maximum number of attempts (30) is reached
77+
- The maximum wait time (60 seconds) is exceeded
78+
79+
The exponential backoff ensures that we don't overwhelm the server with requests while still checking frequently enough to catch status changes quickly.
80+
81+
.. _tenacity: https://github.com/jd/tenacity
82+

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
response
77
request
88
IPAM
9+
branching
910
advanced
1011

1112
TL;DR

pynetbox/core/api.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
limitations under the License.
1515
"""
1616

17+
import contextlib
18+
1719
import requests
1820

1921
from pynetbox.core.app import App, PluginsApp
@@ -208,3 +210,31 @@ def create_token(self, username, password):
208210
# object details will fail
209211
self.token = resp["key"]
210212
return Record(resp, self, None)
213+
214+
@contextlib.contextmanager
215+
def activate_branch(self, branch):
216+
"""
217+
Context manager to activate the branch by setting the schema ID in the headers.
218+
219+
:Raises: ValueError if the branch is not a valid NetBox branch.
220+
221+
:Example:
222+
223+
>>> import pynetbox
224+
>>> nb = pynetbox.api("https://netbox-server")
225+
>>> branch = nb.plugins.branching.branches.create(name="testbranch")
226+
>>> with nb.activate_branch(branch):
227+
... sites = nb.dcim.sites.all()
228+
... # All operations within this block will use the branch's schema
229+
"""
230+
if not isinstance(branch, Record) or not "schema_id" in dict(branch):
231+
raise ValueError(
232+
f"The specified branch is not a valid NetBox branch: {branch}."
233+
)
234+
235+
self.http_session.headers["X-NetBox-Branch"] = branch.schema_id
236+
237+
try:
238+
yield
239+
finally:
240+
self.http_session.headers.pop("X-NetBox-Branch", None)

0 commit comments

Comments
 (0)