Topolograph Python SDK
A Pythonic, object-oriented client for the Topolograph REST API with built-in topology ingestion via SSH.
Features
- Pythonic API Client: Clean, object-oriented interface to the Topolograph REST API
- SSH-Based Collection: Collect IGP LSDB data directly from network devices using Nornir
- CLI Interface: Command-line tool built on top of the SDK
- PyPI Ready: Installable via
pip install topolograph-sdk
Installation
pip install topolograph-sdk
PyPI Package: https://pypi.org/project/topolograph-sdk/
Quick Start
Basic Usage
from topolograph import Topolograph
# Initialize client
topo = Topolograph(
url="http://localhost:8080",
token="your-api-token" # or set TOPOLOGRAPH_TOKEN env var
)
# Get latest graph
graph = topo.graphs.get(latest=True)
# Access graph properties
print(f"Graph Time: {graph.graph_time}")
print(f"Protocols: {graph.protocols}")
print(f"Hosts: {graph.hosts['count']}")
# Get graph status
status = graph.status()
print(f"Status: {status['status']}")
Collecting Topology Data
from topolograph import TopologyCollector
# Create collector with inventory file
collector = TopologyCollector("inventory.yaml")
# Collect LSDB data
result = collector.collect()
# Access results
print(f"Collected from {len(result.host_results)} hosts")
print(f"LSDB text length: {len(result.raw_lsdb_text)}")
# Upload to Topolograph
graph = topo.uploader.upload_raw(
lsdb_text=result.raw_lsdb_text,
vendor="FRR",
protocol="isis"
)
Inventory Format
Create a YAML inventory file with explicit vendor and protocol metadata. A sample inventory file (inventory.yaml.example) is provided in the project root:
---
router1:
hostname: 172.20.20.2
username: admin
password: admin
vendor: frr
protocol: isis
port: 22
router2:
hostname: 172.20.20.3
username: admin
password: admin
vendor: cisco
protocol: ospf
port: 22
Required fields:
hostname: IP address or hostname of the deviceusername: SSH usernamepassword: SSH passwordvendor: Device vendor (cisco,juniper,frr,arista,nokia,huawei)protocol: IGP protocol (ospf,isis)port: SSH port (optional, defaults to 22)
Quick start: Copy the example inventory file:
cp inventory.yaml.example inventory.yaml
# Edit inventory.yaml with your device credentials
Working with Graphs
# List all graphs
graphs = topo.graphs.list(protocol="ospf")
# Get specific graph
graph = topo.graphs.get_by_time("2024-01-15T10:30:00Z")
# Nodes: all(), filter(**kw), get(name=...), count(**kw); pages are fetched while iterating
for node in graph.nodes.all():
print(node["node_id"], node["display_name"])
abrs = list(graph.nodes.filter(abr=True))
node = graph.nodes.get(name="10.0.0.1") # one node or None
# Find networks
networks = graph.networks.find_by_ip("10.10.10.1")
networks = graph.networks.find_by_node("1.1.1.1")
network = graph.networks.find_by_network("10.10.10.0/24")
Uploading YAML Diagrams
Upload arbitrary network topologies defined in YAML format:
# Define topology in YAML format
yaml_diagram = """
nodes:
10.10.10.1:
label: Router1
location: dc1
10.10.10.2:
label: Router2
location: dc1
edges:
- src: 10.10.10.1
dst: 10.10.10.2
cost: 10
bw: 1000
"""
# Upload diagram
graph = topo.graphs.upload_diagram(yaml_diagram)
print(f"Diagram uploaded with graph_time: {graph.graph_time}")
Updating Node Attributes
Update node attributes in YAML-based diagrams:
# Get a YAML diagram graph
graph = topo.graphs.get_by_time("18Jan2026_15h53m13s_3_hosts_yaml")
# Get a node
node = graph.nodes.get_by_id(0)
print(f"Current name: {node.name}")
# Update node completely (PUT - replaces all attributes)
updated_node = graph.nodes.update(
node.id,
{
'name': 'new_router_name',
'location': 'datacenter1',
'role': 'core_router',
'vendor': 'cisco'
}
)
# Partially update node (PATCH - only specified attributes)
updated_node = graph.nodes.patch(
node.id,
{'name': 'renamed_router'}
)
# Or use instance methods for convenience
node = graph.nodes.get_by_id(0)
updated_node = node.patch(name='new_name', location='dc2')
Path Computation
# Shortest path between nodes
path = graph.paths.shortest("1.1.1.1", "2.2.2.2")
print(f"Path cost: {path.cost}")
for path_nodes in path.paths:
print(f"Path: {' -> '.join(path_nodes)}")
# Shortest path between networks/IPs
path = graph.paths.shortest_network("192.168.1.1", "192.168.2.1")
# Backup path (removing specific edges)
path = graph.paths.shortest(
"1.1.1.1",
"2.2.2.2",
removed_edges=[("1.1.1.1", "3.3.3.3")]
)
Events
# Get network events
network_events = graph.events.networks(last_minutes=60)
for event in network_events['network_up_down_events']:
print(f"Network {event.event_object} is {event.event_status}")
# Get adjacency events
adjacency_events = graph.events.adjacency(
start_time="2024-01-15T10:00:00Z",
end_time="2024-01-15T11:00:00Z"
)
BGP topology
Requires Topolograph >= 2.69.
# List BGP epochs, take the newest
bgp = client.bgp_graphs.get_latest()
# Speakers and sessions
bgp.nodes.list(role="rr")
bgp.sessions.list(bgp_session_type="ebgp")
# Route table (one row per RFC 4271 9.1 path); pass router_id to scope it
# to that router's resolved RIB view
bgp.routes.search(prefix="10.0.0.0/24", community="65000:100")
bgp.routes.search(router_id="1.1.1.1", ribs="loc-rib")
bgp.routes.summary("1.1.1.1")
# Point-in-time state and a two-timestamp diff
bgp.routes.state(at="2026-08-30T10:00:00Z")
bgp.routes.compare("2026-08-30T09:00:00Z", "2026-08-30T10:00:00Z")
# BGP change events, IGP bindings
bgp.events.timeline(last_minutes=15)
bgp.igp_bindings.list()
# VRF inventory for an IGP graph's routers
graph.vrfs(router_id="1.1.1.1")
# Protocol-aware path resolution (static/BGP/IGP/LSP hand-off)
graph.paths.resolve_route("R1", "8.8.8.8", vrf="BLUE")
BGP/EVPN over the IGP graph
Every VPN/EVPN question is answered from the IGP graph the BGP epoch is bound
to -- no separate bgp_graph_time needed.
# VNI/VRF inventory, fabric-wide or as one router sees it
graph.vpns.all()
graph.vpns.filter(router_id="leaf1")
# Leaves that carry a VNI or VRF
graph.nodes.filter(protocol="bgp", vni=1010)
# Routes: VRF/VNI contents, or where a MAC/IP currently is
graph.routes.filter(mac="aa:bb:cc:00:00:01")
graph.routes.filter(router_id="leaf1", vrf="tenant1")
graph.routes.count(vni=1010)
# History: did a MAC move, from which VTEP to which
for event in graph.events.routes.filter(mac="aa:bb:cc:00:00:01", last_minutes=60):
print(event.get("moved_from_vtep"))
# Path to several VTEPs at once (underlay colouring)
graph.paths.shortest_to_many("leaf1", ["leaf2", "leaf3"])
CLI Usage
The SDK includes a CLI tool accessible via the topo command:
List Graphs
# List all graphs
topo graphs --list
# Get latest graph
topo graphs --latest
# Filter by protocol
topo graphs --list --protocol ospf
# Filter by watcher
topo graphs --list --watcher production-watcher
Collect and Upload Topology
# Collect LSDB from devices
topo ingest inventory.yaml --protocol isis
# Collect and save to file
topo ingest inventory.yaml --output lsdb.txt
# Collect and upload to Topolograph
topo ingest inventory.yaml --upload --url http://localhost:8080
Compute Paths
# Shortest path between nodes
topo path --src 1.1.1.1 --dst 2.2.2.2
# Shortest path between networks
topo path --src 192.168.1.1 --dst 192.168.2.1 --network
# Use specific graph
topo path --src 1.1.1.1 --dst 2.2.2.2 --graph-time "2024-01-15T10:30:00Z"
Upload LSDB Files
# Upload a LSDB file
topo upload --file lsdb.txt --vendor FRR --protocol isis
# Upload with watcher name
topo upload --file lsdb.txt --vendor Cisco --protocol ospf --watcher prod-watcher
Supported Vendors and Protocols
OSPF
- Cisco:
show ip ospf database router,show ip ospf database network,show ip ospf database external - Juniper:
show ospf database router extensive | no-more, etc. - FRR/Quagga:
show ip ospf database router, etc. - Arista:
show ip ospf database router detail, etc. - Nokia:
show router ospf database router detail, etc.
IS-IS
- Cisco:
show isis database detail - Juniper:
show isis database extensive - FRR:
show isis database detail - Nokia:
show router isis database detail - Huawei:
display isis lsdb verbose
Traffic Engineering attributes
Edges carry the TE attributes advertised by the routers: temetric, admin_group,
max_link_bw, max_rsrv_link_bw, unreserved_bw_0 … unreserved_bw_7, srlg, and
is_te_link (true when at least one TE value is present). Filter them with range
operators and run a constrained path with cspf_path, optionally for one IS-IS level:
edges = graph.edges_list(temetric__gte=100)
te_links = graph.edges_list(is_te_link=True)
path = graph.cspf_path("10.10.10.1", "10.10.10.7", bandwidth="5G", level=2)
Which vendor's output provides which attribute, with the RFC section of each, is in TE attributes by vendor. A step-by-step example with SDK calls on a 13-router IS-IS lab is in the IS-IS runbook.
Authentication
The SDK supports multiple authentication methods (in priority order):
-
Explicit token parameter:
topo = Topolograph(url="...", token="your-token")
-
Environment variable:
export TOPOLOGRAPH_TOKEN="your-token"
-
Basic authentication:
topo = Topolograph(url="...", username="user", password="pass")
Error Handling
The SDK raises custom exceptions for different error scenarios:
from topolograph.exceptions import (
AuthenticationError,
NotFoundError,
ValidationError,
APIError
)
try:
graph = topo.graphs.get_by_time("invalid-time")
except NotFoundError:
print("Graph not found")
except AuthenticationError:
print("Authentication failed")
except APIError as e:
print(f"API error: {e}")
Testing
Run integration tests with containerlab:
# Set environment variables
export TOPOLOGRAPH_URL="http://localhost:8080"
export TOPOLOGRAPH_TOKEN="your-token"
# Run tests
pytest tests/test_integration.py -v
Or use the manual test script:
python test_sdk.py
Development
Setup Development Environment
# Clone repository
git clone https://github.com/topolograph/topolograph-sdk.git
cd topolograph-sdk
# Install in development mode
pip install -e ".[dev]"
Project Structure
topolograph-sdk/
├── topolograph/ # SDK package
│ ├── client.py # Core HTTP client
│ ├── resources/ # Resource objects (Graph, Node, Network, etc.)
│ ├── collector/ # SSH-based topology collection
│ └── upload/ # Upload pipeline
├── cli/ # CLI interface
├── tests/ # Test suite
└── pyproject.toml # Package configuration
License
Apache License 2.0 - See LICENSE file for details.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
For issues, questions, or feature requests, please open an issue on GitHub.
Metadata
Release files for topolograph-sdk 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| topolograph_sdk-0.2.1.tar.gz | 49.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| topolograph_sdk-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 91.6 kB
Release files / topolograph_sdk-0.2.1.tar.gz
| Download URL | topolograph_sdk-0.2.1.tar.gz |
|---|---|
| Size | 49.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b96607c88fdd2e9402cee06c6910f2e01a1f4446420b1428a715d9f36a73ddfe
|
|
BLAKE2b-256 checksum How to use checksums |
4054380ede5b8e5399395615100bad512d8841c18b9b75f8f87da11416ce13a6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.14
|
Release files / topolograph_sdk-0.2.1-py3-none-any.whl
| Download URL | topolograph_sdk-0.2.1-py3-none-any.whl |
|---|---|
| Size | 42.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
79ea47518286e001534ce9bb38c1de7662065c798dd63ac5347b6c313e4d4aaa
|
|
BLAKE2b-256 checksum How to use checksums |
fe0dfb7f67aa24fac604fd5e5805d4b1c9136f62cc3d7ef4db87583865218947
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.14
|
Security for topolograph-sdk 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.