Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,69 @@ root.core.switch_config.post({ "name": "leaf-1a", "mac-address": "01:02:03:04:05
root.core.switch.match(name="leaf-1a").disconnect.rpc()
```

---
## Retry Configuration

pybsn supports automatic retry of failed HTTP requests to improve reliability when connecting to BigDB. You can configure retry behavior using the `retries` parameter in `pybsn.connect()`.

### Simple Retry Count

Pass an integer to specify retry attempts for **connection-level failures only**:

```python
# Retry up to 3 times on connection errors, timeouts, DNS failures
client = pybsn.connect(host="controller", token="<token>", retries=3)
```

**Important:** When using an integer value:
- Retries **GET, HEAD, OPTIONS, PUT, DELETE, TRACE** requests (but **NOT POST or PATCH**)
- Retries only on **connection-level failures** (connection errors, timeouts, DNS failures)
- Does **NOT** retry on HTTP error status codes like 503 or 504
- No exponential backoff (immediate retry)

To retry on HTTP status codes or include POST/PATCH requests, use a `Retry` object (see below).

### Advanced Retry Configuration

For more control over retry behavior, use a `urllib3.util.retry.Retry` object:

```python
from urllib3.util.retry import Retry

# Configure custom retry behavior
retry_config = Retry(
total=5, # Total retry attempts
backoff_factor=1, # Exponential backoff (1s, 2s, 4s, 8s, 16s)
status_forcelist=[503, 504], # Retry on specific HTTP status codes
)

client = pybsn.connect(host="controller", token="<token>", retries=retry_config)
```

### Retry Non-Idempotent Methods

**Use with caution!** To retry non-idempotent methods (POST, PUT, PATCH, DELETE), explicitly configure `allowed_methods`:

```python
from urllib3.util.retry import Retry

# WARNING: Only use if your operations are truly idempotent!
retry_config = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[503],
allowed_methods=["GET", "POST", "PUT", "PATCH", "DELETE"], # Include all methods
)

client = pybsn.connect(host="controller", token="<token>", retries=retry_config)
```

### Default Behavior

By default (`retries=None`), no automatic retries are performed. Requests fail immediately on errors, preserving backward compatibility with existing code.

For more examples, see `examples/retry_example.py`.

---
## Contributing

Expand Down
98 changes: 98 additions & 0 deletions examples/retry_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python
"""
Example demonstrating retry configuration with pybsn.connect()

This example shows how to configure automatic retries for failed HTTP requests
to improve reliability when connecting to BigDB.
"""
import argparse

from urllib3.util.retry import Retry

import pybsn

parser = argparse.ArgumentParser(description="Demonstrate retry configuration")
parser.add_argument("--host", "-H", type=str, default="127.0.0.1", help="Controller IP/Hostname to connect to")
parser.add_argument("--user", "-u", type=str, default="admin", help="Username")
parser.add_argument("--password", "-p", type=str, default="adminadmin", help="Password")

args = parser.parse_args()


def example_simple_retry():
"""Example 1: Simple integer retry count (retries on connection-level failures only)"""
print("Example 1: Simple integer retry count")
client = pybsn.connect(args.host, args.user, args.password, retries=3)

# This will retry up to 3 times on connection errors, timeouts, DNS failures
# Retries GET, HEAD, OPTIONS, PUT, DELETE, TRACE (but NOT POST or PATCH)
# Does NOT retry on HTTP status codes like 503 - only connection-level failures
switches = client.root.core.switch()
print(f" Found {len(switches)} switches")


def example_custom_retry():
"""Example 2: Custom Retry object with specific configuration"""
print("\nExample 2: Custom Retry with status codes and backoff")

# Configure retry with:
# - 5 total retry attempts
# - Exponential backoff with 1 second base delay
# - Retry on specific HTTP status codes (503, 504)
retry_config = Retry(
total=5,
backoff_factor=1, # Wait 1s, 2s, 4s, 8s, 16s between retries
status_forcelist=[503, 504], # Retry on Service Unavailable and Gateway Timeout
)

client = pybsn.connect(args.host, args.user, args.password, retries=retry_config)

# This will retry on 503/504 errors with exponential backoff
switches = client.root.core.switch()
print(f" Found {len(switches)} switches with custom retry config")


def example_retry_non_idempotent():
"""Example 3: Retry non-idempotent methods (use with caution!)"""
print("\nExample 3: Retry non-idempotent methods (POST, PUT, PATCH, DELETE)")

# By default, only safe/idempotent methods are retried
# To retry POST/PUT/PATCH/DELETE, explicitly configure allowed_methods
# WARNING: Only use this if your operations are truly idempotent!
retry_config = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[503],
allowed_methods=["GET", "POST", "PUT", "PATCH", "DELETE"], # Include non-idempotent methods
)

client = pybsn.connect(args.host, args.user, args.password, retries=retry_config)

# Now POST/PUT/PATCH/DELETE operations will also retry on failures
# Use with caution - ensure your operations are idempotent!
switches = client.root.core.switch()
print(f" Found {len(switches)} switches (with non-idempotent retry enabled)")


def example_no_retry():
"""Example 4: No retry (default behavior)"""
print("\nExample 4: No retry (default behavior)")

# Default behavior - no automatic retries
client = pybsn.connect(args.host, args.user, args.password)

# Failures will raise immediately without retry
switches = client.root.core.switch()
print(f" Found {len(switches)} switches (no retry)")


if __name__ == "__main__":
print("=== pybsn Retry Configuration Examples ===\n")

# Run all examples
example_simple_retry()
example_custom_retry()
example_retry_non_idempotent()
example_no_retry()

print("\n=== Examples completed ===")
22 changes: 22 additions & 0 deletions pybsn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import requests
import urllib3.util
from urllib3.exceptions import InsecureRequestWarning
from urllib3.util.retry import Retry

warnings.simplefilter("ignore", InsecureRequestWarning)

Expand All @@ -23,6 +24,7 @@
JSONPrimitive = Union[None, bool, int, float, str]
JSONValue = Union[JSONPrimitive, Dict[str, Any], List[Any]] # Any allows nested structures
TimeoutType = Union[None, float, "_ClientTimeout", urllib3.util.Timeout]
RetriesType = Union[None, int, Retry]


class _ClientTimeout:
Expand Down Expand Up @@ -622,6 +624,7 @@ def connect(
verify_tls: bool = False,
session_headers: Optional[Dict[str, str]] = None,
timeout: Optional[Union[float, urllib3.util.Timeout]] = None,
retries: RetriesType = None,
) -> BigDbClient:
"""Creates a connected BigDb client.

Expand All @@ -645,6 +648,19 @@ def connect(
to wait forever. The timeout value will be used as the default
for future operations unless it is changed.

:parameter retries: Optional retry configuration for failed HTTP requests.
None (default) - No automatic retries.
int - Retry count for connection-level failures only (e.g., retries=3).
When an integer is specified, retries GET, HEAD, OPTIONS, PUT, DELETE, TRACE requests
on connection errors, timeouts, and DNS failures.
Does NOT retry on HTTP error status codes (e.g., 503, 504).
Does NOT retry POST or PATCH requests.
No exponential backoff (immediate retry).
urllib3.util.retry.Retry - Full retry configuration object for advanced control.
Use this to retry on HTTP status codes, configure backoff, or retry POST/PATCH.
Example: retries=Retry(total=5, backoff_factor=1, status_forcelist=[503, 504],
allowed_methods=["GET", "POST"]).

(other parameters for advanced/internal use).

:return A connected BigDBClient instance
Expand All @@ -656,6 +672,12 @@ def connect(
for k, v in session_headers.items():
session.headers[k] = v

# Mount HTTPAdapter with retry configuration if specified
if retries is not None:
adapter = requests.adapters.HTTPAdapter(max_retries=retries)
session.mount("http://", adapter)
session.mount("https://", adapter)

url = guess_url(session, host)
if login is None:
login = (token is None) and username is not None and password is not None
Expand Down
Loading
Loading