Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/py3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python: ["3.7", "3.10"]
netbox: ["2.11", "3.0", "3.1"]
python: ["3.8", "3.9", "3.10"]
netbox: ["3.3"]

steps:
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Pynetbox
Python API client library for [NetBox](https://github.com/netbox-community/netbox).

> **Note:** Version 6.7 and later of the library only supports NetBox 3.3 and above.

## Installation

Expand Down
2 changes: 1 addition & 1 deletion pynetbox/core/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __init__(self, req):
)
)

super().__init__(message)
super().__init__(self.message)
self.req = req
self.request_body = req.request.body
self.base = req.url
Expand Down
26 changes: 24 additions & 2 deletions pynetbox/core/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,22 @@ def _parse_values(self, values):
values within.
"""

def generic_list_parser(key_name, list_item):
from pynetbox.models.mapper import CONTENT_TYPE_MAPPER

if (
isinstance(list_item, dict)
and "object_type" in list_item
and "object" in list_item
):
lookup = list_item["object_type"]
model = None
model = CONTENT_TYPE_MAPPER.get(lookup)
if model:
return model(list_item["object"], self.api, self.endpoint)

return list_item

def list_parser(key_name, list_item):
if isinstance(list_item, dict):
lookup = getattr(self.__class__, key_name, None)
Expand All @@ -364,6 +380,7 @@ def list_parser(key_name, list_item):
else:
model = lookup[0]
return model(list_item, self.api, self.endpoint)

return list_item

for k, v in values.items():
Expand All @@ -382,8 +399,13 @@ def list_parser(key_name, list_item):
self._add_cache((k, v))

elif isinstance(v, list):
v = [list_parser(k, i) for i in v]
to_cache = list(v)
# check if GFK
if len(v) and isinstance(v[0], dict) and "object_type" in v[0]:
v = [generic_list_parser(k, i) for i in v]
to_cache = list(v)
else:
v = [list_parser(k, i) for i in v]
to_cache = list(v)
self._add_cache((k, to_cache))

else:
Expand Down
81 changes: 41 additions & 40 deletions pynetbox/models/dcim.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,35 @@


class TraceableRecord(Record):
def _get_obj_class(self, url):
uri_to_obj_class_map = {
"dcim/cables": Cables,
"dcim/front-ports": FrontPorts,
"dcim/interfaces": Interfaces,
"dcim/rear-ports": RearPorts,
}

# the url for this item will be something like:
# https://netbox/api/dcim/rear-ports/12761/
# TODO: Move this to a more general function.
app_endpoint = "/".join(
urlsplit(url).path[len(urlsplit(self.api.base_url).path) :].split("/")[1:3]
)
return uri_to_obj_class_map.get(
app_endpoint,
Record,
)

def _build_termination_data(self, termination_list):
terminations_data = []
for hop_item_data in termination_list:
return_obj_class = self._get_obj_class(hop_item_data["url"])
terminations_data.append(
return_obj_class(hop_item_data, self.endpoint.api, self.endpoint)
)

return terminations_data

def trace(self):
req = Request(
key=str(self.id) + "/trace",
Expand All @@ -31,38 +60,18 @@ def trace(self):
session_key=self.api.session_key,
http_session=self.api.http_session,
).get()
uri_to_obj_class_map = {
"dcim/cables": Cables,
"dcim/front-ports": FrontPorts,
"dcim/interfaces": Interfaces,
"dcim/rear-ports": RearPorts,
}

ret = []
for (termination_a_data, cable_data, termination_b_data) in req:
this_hop_ret = []
for hop_item_data in (termination_a_data, cable_data, termination_b_data):
# if not fully terminated then some items will be None
if not hop_item_data:
this_hop_ret.append(hop_item_data)
continue

# the url for this item will be something like:
# https://netbox/api/dcim/rear-ports/12761/
# TODO: Move this to a more general function.
app_endpoint = "/".join(
urlsplit(hop_item_data["url"])
.path[len(urlsplit(self.api.base_url).path) :]
.split("/")[1:3]
)
return_obj_class = uri_to_obj_class_map.get(
app_endpoint,
Record,
)
this_hop_ret.append(
return_obj_class(hop_item_data, self.endpoint.api, self.endpoint)
for (a_terminations_data, cable_data, b_terminations_data) in req:
ret.append(self._build_termination_data(a_terminations_data))
if not cable_data:
ret.append(cable_data)
else:
return_obj_class = self._get_obj_class(cable_data["url"])
ret.append(
return_obj_class(cable_data, self.endpoint.api, self.endpoint)
)

ret.append(this_hop_ret)
ret.append(self._build_termination_data(b_terminations_data))

return ret

Expand Down Expand Up @@ -225,14 +234,6 @@ def __str__(self):

class Cables(Record):
def __str__(self):
if all(
[
isinstance(i, Termination)
for i in (self.termination_a, self.termination_b)
]
):
return "{} <> {}".format(self.termination_a, self.termination_b)
if len(self.a_terminations) == 1 and len(self.b_terminations) == 1:
return "{} <> {}".format(self.a_terminations[0], self.b_terminations[0])
return "Cable #{}".format(self.id)

termination_a = Termination
termination_b = Termination
118 changes: 118 additions & 0 deletions pynetbox/models/mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from .circuits import Circuits, CircuitTerminations
from .dcim import (
DeviceTypes,
Devices,
Interfaces,
PowerOutlets,
PowerPorts,
ConsolePorts,
ConsoleServerPorts,
RackReservations,
VirtualChassis,
FrontPorts,
RearPorts,
Racks,
Termination,
Cables,
)
from .ipam import (
IpAddresses,
Prefixes,
Aggregates,
Vlans,
VlanGroups,
)
from .virtualization import VirtualMachines
from .wireless import WirelessLans


CONTENT_TYPE_MAPPER = {
"circuits.circuit": Circuits,
"circuits.circuittermination": CircuitTerminations,
"dcim.cable": Cables,
"dcim.cablepath": None,
"dcim.cabletermination": Termination,
"dcim.consoleport": ConsolePorts,
"dcim.consoleporttemplate": None,
"dcim.consoleserverport": ConsoleServerPorts,
"dcim.consoleserverporttemplate": None,
"dcim.device": Devices,
"dcim.devicebay": None,
"dcim.devicebaytemplate": None,
"dcim.devicerole": None,
"dcim.devicetype": DeviceTypes,
"dcim.frontport": FrontPorts,
"dcim.frontporttemplate": None,
"dcim.interface": Interfaces,
"dcim.interfacetemplate": None,
"dcim.inventoryitem": None,
"dcim.inventoryitemrole": None,
"dcim.inventoryitemtemplate": None,
"dcim.location": None,
"dcim.manufacturer": None,
"dcim.module": None,
"dcim.modulebay": None,
"dcim.modulebaytemplate": None,
"dcim.moduletype": None,
"dcim.platform": None,
"dcim.powerfeed": None,
"dcim.poweroutlet": PowerOutlets,
"dcim.poweroutlettemplate": None,
"dcim.powerpanel": None,
"dcim.powerport": ConsolePorts,
"dcim.powerporttemplate": None,
"dcim.rack": Racks,
"dcim.rackreservation": RackReservations,
"dcim.rackrole": None,
"dcim.rearport": RearPorts,
"dcim.rearporttemplate": None,
"dcim.region": None,
"dcim.site": None,
"dcim.sitegroup": None,
"dcim.virtualchassis": VirtualChassis,
"extras.configcontext": None,
"extras.configrevision": None,
"extras.customfield": None,
"extras.customlink": None,
"extras.exporttemplate": None,
"extras.imageattachment": None,
"extras.jobresult": None,
"extras.journalentry": None,
"extras.objectchange": None,
"extras.report": None,
"extras.script": None,
"extras.tag": None,
"extras.taggeditem": None,
"extras.webhook": None,
"ipam.aggregate": Aggregates,
"ipam.ASN": None,
"ipam.FHRPgroup": None,
"ipam.FHRPgroupassignment": None,
"ipam.IPaddress": IpAddresses,
"ipam.IPrange": None,
"ipam.L2VPN": None,
"ipam.L2VPNtermination": None,
"ipam.prefix": Prefixes,
"ipam.RIR": None,
"ipam.role": None,
"ipam.routetarget": None,
"ipam.service": None,
"ipam.servicetemplate": None,
"ipam.VLAN": Vlans,
"ipam.VLANgroup": VlanGroups,
"ipam.VRF": None,
"tenancy.contact": None,
"tenancy.contactassignment": None,
"tenancy.contactgroup": None,
"tenancy.contactrole": None,
"tenancy.tenant": None,
"tenancy.tenantgroup": None,
"virtualization.cluster": None,
"virtualization.clustergroup": None,
"virtualization.clustertype": None,
"virtualization.interface": None,
"virtualization.virtualmachine": VirtualMachines,
"wireless.WirelessLAN": WirelessLans,
"wireless.WirelessLANGroup": None,
"wireless.wirelesslink": None,
}
7 changes: 4 additions & 3 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
black~=22.0
pytest==6.2.*
pytest-docker==0.10.*
black~=22.10
pytest==7.1.*
pytest-docker==1.0.*
PyYAML==6.0
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
setup(
name="pynetbox",
description="NetBox API client library",
url="https://github.com/digitalocean/pynetbox",
url="https://github.com/netbox-community/netbox",
author="Zach Moody",
author_email="zmoody@do.co",
license="Apache2",
Expand All @@ -20,6 +20,8 @@
"Intended Audience :: Developers",
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
)
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from packaging import version


DEFAULT_NETBOX_VERSIONS = "2.11, 3.0, 3.1"
DEFAULT_NETBOX_VERSIONS = "3.3"


def pytest_addoption(parser):
Expand Down
20 changes: 6 additions & 14 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@ def get_netbox_docker_version_tag(netbox_version):
"""
major, minor = netbox_version.major, netbox_version.minor

if (major, minor) == (3, 1):
tag = "1.5.1"
elif (major, minor) == (3, 0):
tag = "1.5.1"
elif (major, minor) == (2, 11):
tag = "1.2.0"
if (major, minor) == (3, 3):
tag = "2.2.0"
else:
raise NotImplementedError(
"Version %s is not currently supported" % netbox_version
Expand Down Expand Up @@ -385,14 +381,10 @@ def docker_netbox_service(
"""
netbox_integration_version = request.param

if netbox_integration_version >= version.Version("2.10"):
netbox_service_name = "netbox_v%s_netbox" % str(
netbox_integration_version
).replace(".", "_")
else:
netbox_service_name = "netbox_v%s_nginx" % str(
netbox_integration_version
).replace(".", "_")
netbox_service_name = "netbox_v%s_netbox" % str(netbox_integration_version).replace(
".", "_"
)

netbox_service_port = 8080
try:
# `port_for` takes a container port and returns the corresponding host port
Expand Down
Loading