nbforager 0.2.2__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nbforager-0.2.2 → nbforager-0.2.6}/PKG-INFO +4 -4
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/__init__.py +3 -1
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/base_c.py +15 -7
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/connector.py +1 -1
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/forager.py +6 -40
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/joiner.py +64 -45
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/helpers.py +14 -1
- nbforager-0.2.6/nbforager/models/__init__.py +1 -0
- nbforager-0.2.6/nbforager/models/dcim/__init__.py +1 -0
- nbforager-0.2.6/nbforager/models/dcim/interfaces/__init__.py +1 -0
- nbforager-0.2.6/nbforager/models/dcim/interfaces/interface_nb.py +13 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_cache.py +4 -6
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_forager.py +3 -2
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_tree.py +23 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/nb_parser.py +39 -2
- {nbforager-0.2.2 → nbforager-0.2.6}/pyproject.toml +7 -5
- {nbforager-0.2.2 → nbforager-0.2.6}/LICENCE.txt +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/README.rst +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/__init__.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/base_ca.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/circuits.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/core.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/dcim.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/extended_get.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/extras.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/ip_addresses.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/ipam.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/plugins_ca.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/status.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/tenancy.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/users.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/virtualization.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/wireless.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/exceptions.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/__init__.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/base_fa.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/circuits.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/core.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/dcim.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/extras.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/ipam.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/ipv4.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/tenancy.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/users.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/virtualization.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/wireless.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/log.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/messages.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_api.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/__init__.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/nb_custom.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/nb_value.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/py.typed +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/py_tree.py +0 -0
- {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/types_.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nbforager
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.6
|
4
4
|
Summary: Python package designed to assist in working with the Netbox REST API. The filter parameters are identical to those in the Web UI filter form. It replaces brief data with full information, and Netbox objects are represented as a recursive multidimensional dictionary.
|
5
5
|
Home-page: https://github.com/vladimirs-git/nbforager
|
6
6
|
License: Apache-2.0
|
@@ -20,15 +20,15 @@ Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
21
21
|
Provides-Extra: test
|
22
22
|
Requires-Dist: ciscoconfparse (>=1.9,<2.0)
|
23
|
-
Requires-Dist: netports (>=0.12.1,<0.
|
23
|
+
Requires-Dist: netports (>=0.12.1,<1.0.0)
|
24
24
|
Requires-Dist: pydantic (>=2,<3)
|
25
25
|
Requires-Dist: pynetbox (>=7.3.3,<8.0.0)
|
26
26
|
Requires-Dist: requests (>=2,<3)
|
27
27
|
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
28
28
|
Requires-Dist: tomli (==2.0.1)
|
29
|
-
Requires-Dist: vhelpers (>=0.1,<0.
|
29
|
+
Requires-Dist: vhelpers (>=0.1,<1.0.0)
|
30
30
|
Project-URL: Bug Tracker, https://github.com/vladimirs-git/nbforager/issues
|
31
|
-
Project-URL: Download URL, https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.2.
|
31
|
+
Project-URL: Download URL, https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.2.6.tar.gz
|
32
32
|
Project-URL: Repository, https://github.com/vladimirs-git/nbforager
|
33
33
|
Description-Content-Type: text/x-rst
|
34
34
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""nbforager."""
|
2
2
|
|
3
|
+
from nbforager.foragers.joiner import Joiner
|
3
4
|
from nbforager.nb_api import NbApi
|
4
5
|
from nbforager.nb_forager import NbForager
|
5
6
|
from nbforager.nb_tree import NbTree
|
@@ -8,10 +9,11 @@ from nbforager.parser.nb_parser import NbParser
|
|
8
9
|
from nbforager.parser.nb_value import NbValue
|
9
10
|
|
10
11
|
__all__ = [
|
12
|
+
"Joiner",
|
11
13
|
"NbApi",
|
12
|
-
"NbParser",
|
13
14
|
"NbCustom",
|
14
15
|
"NbForager",
|
16
|
+
"NbParser",
|
15
17
|
"NbTree",
|
16
18
|
"NbValue",
|
17
19
|
]
|
@@ -70,9 +70,9 @@ class BaseC:
|
|
70
70
|
"default_get",
|
71
71
|
"loners",
|
72
72
|
]
|
73
|
-
|
73
|
+
_extra_keys: DLStr = {
|
74
74
|
"ipam/": [
|
75
|
-
# ipam
|
75
|
+
# ipam/aggregates, ipam/prefixes, ipam/ip_addresses
|
76
76
|
"_ipv4",
|
77
77
|
"_aggregate",
|
78
78
|
"_super_prefix",
|
@@ -85,6 +85,7 @@ class BaseC:
|
|
85
85
|
"nbnets__subnets",
|
86
86
|
],
|
87
87
|
"dcim/devices/": [
|
88
|
+
# UI interfaces
|
88
89
|
"_interfaces",
|
89
90
|
"_front_ports",
|
90
91
|
"_rear_ports",
|
@@ -95,6 +96,8 @@ class BaseC:
|
|
95
96
|
"_module_bays",
|
96
97
|
"_device_bays",
|
97
98
|
"_inventory_items",
|
99
|
+
# UI virtual_chassis
|
100
|
+
"_vc_members",
|
98
101
|
],
|
99
102
|
"dcim/interfaces/": [
|
100
103
|
"_ip_addresses",
|
@@ -106,6 +109,11 @@ class BaseC:
|
|
106
109
|
"_ip_addresses",
|
107
110
|
],
|
108
111
|
}
|
112
|
+
"""Extra keys are reserved for internal usage by this package.
|
113
|
+
|
114
|
+
The key represents the app/model path, while the value is the key name that can be used
|
115
|
+
to store additional data generated by this package.
|
116
|
+
"""
|
109
117
|
|
110
118
|
def __init__(self, **kwargs):
|
111
119
|
"""Init BaseC.
|
@@ -536,8 +544,8 @@ class BaseC:
|
|
536
544
|
}
|
537
545
|
return headers
|
538
546
|
|
539
|
-
def
|
540
|
-
"""Check if
|
547
|
+
def _check_extra_keys(self, items: LDAny) -> None:
|
548
|
+
"""Check if extra keys are present in the data.
|
541
549
|
|
542
550
|
The Netbox REST API returns the object as a dictionary.
|
543
551
|
NbForager inject extra key/value pairs into Netbox object.
|
@@ -548,10 +556,10 @@ class BaseC:
|
|
548
556
|
"""
|
549
557
|
for data in items:
|
550
558
|
url_o: ParseResult = urllib.parse.urlparse(data["url"])
|
551
|
-
for path,
|
559
|
+
for path, extra_keys in self._extra_keys.items():
|
552
560
|
if url_o.path.find(path) > -1:
|
553
|
-
if keys := set(
|
554
|
-
raise NbApiError(f"NbForager
|
561
|
+
if keys := set(extra_keys).intersection(data):
|
562
|
+
raise NbApiError(f"NbForager extra {keys=} detected in {self.url}.")
|
555
563
|
|
556
564
|
def _slice_params_counters(self, results: LDAny) -> LDAny:
|
557
565
|
"""Generate sliced parameters based on counts in results.
|
@@ -118,7 +118,7 @@ class Connector(BaseC):
|
|
118
118
|
"""
|
119
119
|
params_ld: LDList = self._validate_params(**kwargs)
|
120
120
|
items: LDAny = self._query_params_ld(params_ld)
|
121
|
-
self.
|
121
|
+
self._check_extra_keys(items=items)
|
122
122
|
return items
|
123
123
|
|
124
124
|
# noinspection PyIncorrectDocstring
|
@@ -10,14 +10,14 @@ from queue import Queue
|
|
10
10
|
from threading import Thread
|
11
11
|
from urllib.parse import urlparse, parse_qs
|
12
12
|
|
13
|
-
from vhelpers import
|
13
|
+
from vhelpers import vstr
|
14
14
|
|
15
15
|
from nbforager import helpers as h
|
16
16
|
from nbforager.nb_api import NbApi
|
17
17
|
from nbforager.nb_tree import NbTree, missed_urls
|
18
|
-
from nbforager.parser.nb_parser import
|
18
|
+
from nbforager.parser.nb_parser import find_objects
|
19
19
|
from nbforager.py_tree import PyTree
|
20
|
-
from nbforager.types_ import LDAny, DiDAny, LStr, LT2StrDAny, DList, LDList,
|
20
|
+
from nbforager.types_ import LDAny, DiDAny, LStr, LT2StrDAny, DList, LDList, DiAny
|
21
21
|
|
22
22
|
|
23
23
|
class Forager:
|
@@ -134,7 +134,7 @@ class Forager:
|
|
134
134
|
|
135
135
|
:return: Filtered Netbox objects.
|
136
136
|
"""
|
137
|
-
return
|
137
|
+
return find_objects(objects=list(self.root_d.values()), **kwargs)
|
138
138
|
|
139
139
|
def find_rse(self, role: str = "", site: str = "", env: str = "", **kwargs) -> LDAny:
|
140
140
|
"""Find Netbox objects in NbForager.tree by Role-Sile-Env finding parameters.
|
@@ -155,7 +155,7 @@ class Forager:
|
|
155
155
|
}
|
156
156
|
params = {k: v for k, v in params.items() if v}
|
157
157
|
kwargs.update(params)
|
158
|
-
return
|
158
|
+
return find_objects(objects=list(self.tree_d.values()), **kwargs)
|
159
159
|
|
160
160
|
def find_tree(self, **kwargs) -> LDAny:
|
161
161
|
"""Find Netbox objects in NbForager.tree by extended finding parameters.
|
@@ -167,7 +167,7 @@ class Forager:
|
|
167
167
|
|
168
168
|
:return: Filtered Netbox objects.
|
169
169
|
"""
|
170
|
-
return
|
170
|
+
return find_objects(objects=list(self.tree_d.values()), **kwargs)
|
171
171
|
|
172
172
|
# ============================= helpers ==============================
|
173
173
|
|
@@ -339,37 +339,3 @@ class Forager:
|
|
339
339
|
app, model = h.path_to_attrs(path)
|
340
340
|
data = getattr(getattr(self.root, app), model)
|
341
341
|
return data
|
342
|
-
|
343
|
-
|
344
|
-
def _find(objects: LDAny, **kwargs) -> LDAny:
|
345
|
-
"""Find Netbox objects in tree by extended finding parameters.
|
346
|
-
|
347
|
-
:param objects: Netbox objects where searching is required using kwargs.
|
348
|
-
:param kwargs: Extended filtering parameters.
|
349
|
-
:return: Filtered Netbox objects.
|
350
|
-
"""
|
351
|
-
if not kwargs:
|
352
|
-
return objects
|
353
|
-
(key, values), *key_values = list(kwargs.items())
|
354
|
-
if not isinstance(values, TLists):
|
355
|
-
values = [values]
|
356
|
-
|
357
|
-
objects_: LDAny = []
|
358
|
-
for data in objects:
|
359
|
-
keys = key.split("__")
|
360
|
-
if len(keys) <= 1:
|
361
|
-
keys = [key]
|
362
|
-
if keys[0] == "tags":
|
363
|
-
if len(keys) != 2:
|
364
|
-
raise ValueError(f"{keys=} {len(keys)=} expected 2.")
|
365
|
-
values_ = [d[keys[1]] for d in data["tags"]]
|
366
|
-
if vlist.is_in(values_, values):
|
367
|
-
objects_.append(data)
|
368
|
-
else:
|
369
|
-
value_ = NbParser(data).any(*keys)
|
370
|
-
if value_ in values:
|
371
|
-
objects_.append(data)
|
372
|
-
|
373
|
-
if key_values:
|
374
|
-
objects_ = _find(objects=objects_, **dict(key_values))
|
375
|
-
return objects_
|
@@ -4,10 +4,12 @@ from operator import itemgetter
|
|
4
4
|
from netports import Intf
|
5
5
|
from vhelpers import vlist
|
6
6
|
|
7
|
-
from nbforager
|
7
|
+
from nbforager import helpers as h
|
8
8
|
from nbforager.api.base_c import BaseC
|
9
|
+
from nbforager.foragers.forager import find_objects
|
9
10
|
from nbforager.foragers.ipv4 import IPv4
|
10
11
|
from nbforager.nb_tree import NbTree
|
12
|
+
from nbforager.parser.nb_value import NbValue
|
11
13
|
from nbforager.types_ import LDAny, DAny, LStr, DiDAny, LInt, DiLDAny
|
12
14
|
|
13
15
|
|
@@ -35,11 +37,11 @@ class Joiner:
|
|
35
37
|
("virtualization", "virtual_machines"),
|
36
38
|
("virtualization", "interfaces"),
|
37
39
|
]:
|
38
|
-
key = f"{app}/{model}/"
|
39
|
-
|
40
|
+
key = h.attr_to_model(f"{app}/{model}/")
|
41
|
+
extra_keys: LStr = BaseC._extra_keys[key] # pylint: disable=W0212
|
40
42
|
objects_d: DiDAny = getattr(getattr(self.tree, app), model)
|
41
43
|
for object_d in objects_d.values():
|
42
|
-
for _key in
|
44
|
+
for _key in extra_keys:
|
43
45
|
object_d[_key] = {}
|
44
46
|
|
45
47
|
for model, key, strict in [
|
@@ -60,7 +62,7 @@ class Joiner:
|
|
60
62
|
data["_sub_prefixes"] = [] # LDAny
|
61
63
|
data["_ip_addresses"] = [] # LDAny
|
62
64
|
|
63
|
-
def join_dcim_devices(self) -> None:
|
65
|
+
def join_dcim_devices(self, **kwargs) -> None:
|
64
66
|
"""Create additional keys to represent dcim.devices similar to the WEB UI.
|
65
67
|
|
66
68
|
In dcim.devices:
|
@@ -75,54 +77,61 @@ class Joiner:
|
|
75
77
|
- ``_power_outlets``
|
76
78
|
- ``_power_ports``
|
77
79
|
- ``_rear_ports``
|
80
|
+
- ``_vc_members``
|
78
81
|
|
79
82
|
In dcim.interfaces:
|
80
83
|
|
81
84
|
- ``_ip_addresses``
|
82
85
|
|
86
|
+
:param kwargs: Filtering parameters.
|
87
|
+
|
83
88
|
:return: None. Update NbTree object.
|
84
89
|
"""
|
85
|
-
self.
|
86
|
-
self.
|
90
|
+
self._join_virtual_chassis(**kwargs)
|
91
|
+
intf_ids: LInt = self._join_dcim_devices(**kwargs)
|
92
|
+
self._join_interfaces_ip(intf_ids, app="dcim")
|
87
93
|
|
88
|
-
def
|
89
|
-
"""
|
94
|
+
def _join_virtual_chassis(self, **kwargs):
|
95
|
+
"""Add virtual-chassis members to master devices.
|
90
96
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
:param kwargs: Additional keyword arguments to filter devices.
|
98
|
+
:return: None. Update data in object.
|
99
|
+
"""
|
100
|
+
app = "dcim"
|
101
|
+
model = "devices"
|
102
|
+
devices_d: DiDAny = getattr(getattr(self.tree, app), model)
|
103
|
+
filtered_d = {d["id"]: d for d in find_objects(objects=list(devices_d.values()), **kwargs)}
|
96
104
|
|
97
|
-
|
105
|
+
for member_id, device_d in filtered_d.items():
|
106
|
+
if device_d["virtual_chassis"]:
|
107
|
+
master_id = device_d["virtual_chassis"]["master"]["id"]
|
108
|
+
if member_id != master_id:
|
109
|
+
if master_d := devices_d.get(master_id, {}):
|
110
|
+
master_d["_vc_members"][member_id] = devices_d[member_id]
|
98
111
|
|
99
|
-
|
100
|
-
"""
|
101
|
-
self._join_dcim_devices(app="virtualization")
|
102
|
-
self._join_dcim_interfaces(app="virtualization")
|
112
|
+
def _join_dcim_devices(self, **kwargs) -> LInt:
|
113
|
+
"""Create additional key/values to represent devices similar to the WEB UI.
|
103
114
|
|
104
|
-
|
105
|
-
def _join_dcim_devices(self, app: str) -> None:
|
106
|
-
"""Create additional keys to represent devices/VM similar to the WEB UI.
|
115
|
+
Create key/values: _interfaces, _front_ports, _console_ports, etc.
|
107
116
|
|
108
|
-
:param
|
117
|
+
:param kwargs: Filtering parameters.
|
109
118
|
|
110
|
-
:return:
|
119
|
+
:return: IDs of joined ports. Update NbTree.dcim.devices object.
|
111
120
|
"""
|
121
|
+
# models
|
122
|
+
app = "dcim"
|
112
123
|
model = "devices"
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
124
|
+
# noinspection PyProtectedMember
|
125
|
+
extra_keys: LStr = BaseC._extra_keys["dcim/devices/"] # pylint: disable=W0212
|
126
|
+
extra_keys = [s for s in extra_keys if s != "_vc_members"]
|
127
|
+
models: LStr = [s.lstrip("_") for s in extra_keys]
|
128
|
+
|
129
|
+
# filter devices
|
118
130
|
devices_d: DiDAny = getattr(getattr(self.tree, app), model)
|
131
|
+
devices_d = {d["id"]: d for d in find_objects(objects=list(devices_d.values()), **kwargs)}
|
119
132
|
|
120
|
-
#
|
121
|
-
key = "device"
|
122
|
-
if app == "virtualization":
|
123
|
-
key = "virtual_machine"
|
133
|
+
intf_ids: LInt = [] # joined interfaces
|
124
134
|
|
125
|
-
models = [s.lstrip("_") for s in reserved_keys]
|
126
135
|
for model in models:
|
127
136
|
ports_d: DiDAny = getattr(getattr(self.tree, app), model)
|
128
137
|
ports: LDAny = list(ports_d.values())
|
@@ -132,24 +141,34 @@ class Joiner:
|
|
132
141
|
ports_lt.sort(key=itemgetter(0))
|
133
142
|
ports = [dict(t[1]) for t in ports_lt]
|
134
143
|
|
144
|
+
# set _interfaces, _front_ports, etc.
|
135
145
|
for port_d in ports:
|
136
146
|
name = port_d["name"]
|
137
|
-
id_ = port_d[
|
138
|
-
device_d: DAny = devices_d.get(id_, {})
|
139
|
-
|
140
|
-
if
|
141
|
-
device_d[
|
147
|
+
id_ = port_d["device"]["id"]
|
148
|
+
device_d: DAny = devices_d.get(id_, {})
|
149
|
+
extra_key = f"_{model}"
|
150
|
+
if extra_key in device_d:
|
151
|
+
device_d[extra_key][name] = port_d
|
152
|
+
if model == "interfaces":
|
153
|
+
intf_ids.append(id_)
|
154
|
+
|
155
|
+
return intf_ids
|
156
|
+
|
157
|
+
def _join_interfaces_ip(self, intf_ids: LInt, app: str) -> None:
|
158
|
+
"""Create additional key/values for ipam/ip-addresses.
|
142
159
|
|
143
|
-
|
144
|
-
"""Create additional keys to represent dcim/interfaces with assigned ipam/ip-addresses.
|
160
|
+
Create key/values: _interfaces, _front_ports, _console_ports, etc.
|
145
161
|
|
162
|
+
:param intf_ids: Interface IDs that was joined in device/VM.
|
146
163
|
:param app: Application name: "dcim", "virtualization"
|
147
164
|
|
148
165
|
:return: None. Update NbTree object.
|
149
166
|
"""
|
150
167
|
model = "interfaces"
|
151
168
|
object_type = "virtualization.vminterface" if app == "virtualization" else "dcim.interface"
|
152
|
-
|
169
|
+
intfs_d: DiDAny = getattr(getattr(self.tree, app), model)
|
170
|
+
params = {"id": intf_ids}
|
171
|
+
intfs_d = {d["id"]: d for d in find_objects(objects=list(intfs_d.values()), **params)}
|
153
172
|
|
154
173
|
app = "ipam"
|
155
174
|
model = "ip_addresses"
|
@@ -165,9 +184,9 @@ class Joiner:
|
|
165
184
|
continue
|
166
185
|
if address_d["assigned_object_type"] != object_type:
|
167
186
|
continue
|
168
|
-
|
169
|
-
if _key in
|
170
|
-
|
187
|
+
intf_d: DAny = intfs_d.get(assigned_object_id, {}) # pylint: disable=E1101
|
188
|
+
if _key in intf_d:
|
189
|
+
intf_d[_key][address] = address_d
|
171
190
|
|
172
191
|
def join_ipam_ipv4(self) -> None:
|
173
192
|
"""Create additional keys to represent ipam similar to the WEB UI.
|
@@ -49,6 +49,19 @@ def attr_names(obj: Any) -> LStr:
|
|
49
49
|
return methods
|
50
50
|
|
51
51
|
|
52
|
+
def attr_to_model(attr: str) -> str:
|
53
|
+
"""Convert attribute name to model name.
|
54
|
+
|
55
|
+
:param attr: The attribute name to be converted.
|
56
|
+
|
57
|
+
:return: The converted model name.
|
58
|
+
|
59
|
+
:example:
|
60
|
+
attr_to_model("ip_addresses") -> "ip-addresses"
|
61
|
+
"""
|
62
|
+
return attr.replace("_", "-")
|
63
|
+
|
64
|
+
|
52
65
|
def join_urls(urls: LStr) -> LStr:
|
53
66
|
"""Join URLs by models with list of IDs in query.
|
54
67
|
|
@@ -98,7 +111,7 @@ def model_to_attr(model: str) -> str:
|
|
98
111
|
:example:
|
99
112
|
model_to_attr("ip-addresses") -> "ip_addresses"
|
100
113
|
"""
|
101
|
-
return
|
114
|
+
return model.replace("-", "_")
|
102
115
|
|
103
116
|
|
104
117
|
def nested_urls(nb_objects: LDAny) -> LStr:
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Models"""
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Models.dcim"""
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Models.dcim.interfaces"""
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Netbox.dcim.interfaces model."""
|
2
|
+
from pydantic import BaseModel, Field
|
3
|
+
|
4
|
+
|
5
|
+
class InterfaceNb(BaseModel): # TODO test
|
6
|
+
"""Netbox.dcim.interfaces model."""
|
7
|
+
|
8
|
+
device: int = Field(ge=0, description="Device id")
|
9
|
+
name: str = Field(max_length=100, description="Interface name")
|
10
|
+
type: str = Field(default="1000base-x-sfp", max_length=50, description="Interface type")
|
11
|
+
enabled: bool = Field(default=True, description="Administrative status")
|
12
|
+
mgmt_only: bool = Field(default=False, description="Management interface")
|
13
|
+
description: str = Field(max_length=200, description="Interface description")
|
@@ -61,8 +61,7 @@ class NbCache:
|
|
61
61
|
tree = NbTree(**tree_d)
|
62
62
|
status = dict(cached.get("status") or {})
|
63
63
|
|
64
|
-
|
65
|
-
logging.debug(msg)
|
64
|
+
logging.debug(str(f"Cache loaded from path={self.cache}."))
|
66
65
|
return tree, status
|
67
66
|
|
68
67
|
def write_cache(self) -> None:
|
@@ -79,12 +78,10 @@ class NbCache:
|
|
79
78
|
error = f"{type(ex).__name__}: {ex}"
|
80
79
|
path = (re.findall(r"(\'.+\')$", str(ex)) or [self.cache])[0]
|
81
80
|
cmd = f'"sudo chmod o+rw {path}"'
|
82
|
-
|
83
|
-
logging.error(msg)
|
81
|
+
logging.error(str(f"{error}. Please change permissions by command: {cmd}."))
|
84
82
|
raise type(ex)(*ex.args)
|
85
83
|
|
86
|
-
|
87
|
-
logging.debug(msg)
|
84
|
+
logging.debug(str(f"Cache saved to path={self.cache}."))
|
88
85
|
|
89
86
|
# ====================== helpers ======================
|
90
87
|
|
@@ -99,6 +96,7 @@ class NbCache:
|
|
99
96
|
root_dir = path.resolve().parent
|
100
97
|
if not root_dir.is_dir():
|
101
98
|
root_dir.mkdir(parents=True, exist_ok=True)
|
99
|
+
logging.info(str(f"Directory created {root_dir}."))
|
102
100
|
|
103
101
|
def _create_file(self) -> None:
|
104
102
|
"""Create pickl file for cache with write permissions 666.
|
@@ -6,13 +6,13 @@ from __future__ import annotations
|
|
6
6
|
|
7
7
|
import copy
|
8
8
|
import logging
|
9
|
+
from copy import deepcopy
|
9
10
|
from datetime import datetime
|
10
11
|
from pathlib import Path
|
11
12
|
|
12
13
|
import pynetbox
|
13
14
|
from pynetbox.core.endpoint import Endpoint
|
14
15
|
from vhelpers import vstr
|
15
|
-
from copy import deepcopy
|
16
16
|
|
17
17
|
from nbforager import nb_tree
|
18
18
|
from nbforager.foragers.circuits import CircuitsAF
|
@@ -69,7 +69,7 @@ class NbForager:
|
|
69
69
|
|
70
70
|
:param cache: Path to cache. If the value ends with .pickle,
|
71
71
|
it is the path to a file; otherwise, it is the path to a directory.
|
72
|
-
The default value is NbCache.{
|
72
|
+
The default value is NbCache.{host}.pickle.
|
73
73
|
|
74
74
|
NbApi parameters:
|
75
75
|
|
@@ -301,6 +301,7 @@ class NbForager:
|
|
301
301
|
- ``_power_outlets``
|
302
302
|
- ``_power_ports``
|
303
303
|
- ``_rear_ports``
|
304
|
+
- ``_vc_members``
|
304
305
|
|
305
306
|
In dcim.interfaces, virtualization.interfaces:
|
306
307
|
|
@@ -4,6 +4,7 @@ from copy import deepcopy
|
|
4
4
|
from typing import Optional
|
5
5
|
|
6
6
|
from pydantic import BaseModel, Field
|
7
|
+
from vhelpers import vstr
|
7
8
|
|
8
9
|
from nbforager import helpers as h
|
9
10
|
from nbforager.types_ import DiDAny, LStr, DAny
|
@@ -12,6 +13,17 @@ from nbforager.types_ import DiDAny, LStr, DAny
|
|
12
13
|
class BaseTree(BaseModel):
|
13
14
|
"""Base for BbTree models."""
|
14
15
|
|
16
|
+
def __repr__(self):
|
17
|
+
"""__repr__."""
|
18
|
+
class_ = self.__class__.__name__
|
19
|
+
params = vstr.repr_info(**{s: len(getattr(self, s)) for s in self.models()})
|
20
|
+
return f"<{class_}: {params}>"
|
21
|
+
|
22
|
+
def clear(self) -> None:
|
23
|
+
"""Clear all data in all models."""
|
24
|
+
for model in self.models():
|
25
|
+
getattr(self, model).clear()
|
26
|
+
|
15
27
|
def count(self) -> int:
|
16
28
|
"""Count the number of Netbox objects for all models."""
|
17
29
|
return sum(len(getattr(self, s)) for s in self.models())
|
@@ -199,6 +211,12 @@ class NbTree(BaseModel):
|
|
199
211
|
virtualization: VirtualizationM = Field(default=VirtualizationM())
|
200
212
|
wireless: WirelessM = Field(default=WirelessM())
|
201
213
|
|
214
|
+
def __repr__(self):
|
215
|
+
"""__repr__."""
|
216
|
+
class_ = self.__class__.__name__
|
217
|
+
params = vstr.repr_info(**{s: getattr(self, s).count() for s in self.apps()})
|
218
|
+
return f"<{class_}: {params}>"
|
219
|
+
|
202
220
|
def apps(self) -> LStr:
|
203
221
|
"""Get all application names.
|
204
222
|
|
@@ -209,6 +227,11 @@ class NbTree(BaseModel):
|
|
209
227
|
"""
|
210
228
|
return list(self.__annotations__)
|
211
229
|
|
230
|
+
def clear(self) -> None:
|
231
|
+
"""Clear all data in all models."""
|
232
|
+
for app in self.apps():
|
233
|
+
getattr(self, app).clear()
|
234
|
+
|
212
235
|
def count(self) -> int:
|
213
236
|
"""Count the number of Netbox objects for all models."""
|
214
237
|
return sum(getattr(self, s).count() for s in self.apps())
|
@@ -4,10 +4,10 @@
|
|
4
4
|
from functools import wraps
|
5
5
|
from typing import Any, Type, Dict, List
|
6
6
|
|
7
|
-
from vhelpers import vstr
|
7
|
+
from vhelpers import vstr, vlist
|
8
8
|
|
9
9
|
from nbforager.exceptions import NbParserError
|
10
|
-
from nbforager.types_ import DAny, SeqStr, Int, Str
|
10
|
+
from nbforager.types_ import DAny, SeqStr, Int, Str, LDAny, TLists
|
11
11
|
|
12
12
|
|
13
13
|
def check_strict(method):
|
@@ -297,3 +297,40 @@ def _init_data(data: DAny) -> DAny:
|
|
297
297
|
if isinstance(data, dict):
|
298
298
|
return data
|
299
299
|
raise TypeError(f"{data=} {dict} expected.")
|
300
|
+
|
301
|
+
|
302
|
+
# ============================ functions =============================
|
303
|
+
|
304
|
+
|
305
|
+
def find_objects(objects: LDAny, **kwargs) -> LDAny:
|
306
|
+
"""Find Netbox objects in tree by extended finding parameters.
|
307
|
+
|
308
|
+
:param objects: Netbox objects where searching is required using kwargs.
|
309
|
+
:param kwargs: Extended filtering parameters.
|
310
|
+
:return: Filtered Netbox objects.
|
311
|
+
"""
|
312
|
+
if not kwargs:
|
313
|
+
return objects
|
314
|
+
(key, values), *key_values = list(kwargs.items())
|
315
|
+
if not isinstance(values, TLists):
|
316
|
+
values = [values]
|
317
|
+
|
318
|
+
objects_: LDAny = []
|
319
|
+
for data in objects:
|
320
|
+
keys = key.split("__")
|
321
|
+
if len(keys) <= 1:
|
322
|
+
keys = [key]
|
323
|
+
if keys[0] == "tags":
|
324
|
+
if len(keys) != 2:
|
325
|
+
raise ValueError(f"{keys=} {len(keys)=} expected 2.")
|
326
|
+
values_ = [d[keys[1]] for d in data["tags"]]
|
327
|
+
if vlist.is_in(values_, values):
|
328
|
+
objects_.append(data)
|
329
|
+
else:
|
330
|
+
value_ = NbParser(data).any(*keys)
|
331
|
+
if value_ in values:
|
332
|
+
objects_.append(data)
|
333
|
+
|
334
|
+
if key_values:
|
335
|
+
objects_ = find_objects(objects=objects_, **dict(key_values))
|
336
|
+
return objects_
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "nbforager"
|
3
|
-
version = "0.2.
|
3
|
+
version = "0.2.6"
|
4
4
|
description = "Python package designed to assist in working with the Netbox REST API. The filter parameters are identical to those in the Web UI filter form. It replaces brief data with full information, and Netbox objects are represented as a recursive multidimensional dictionary."
|
5
5
|
authors = ["Vladimirs Prusakovs <vladimir.prusakovs@gmail.com>"]
|
6
6
|
readme = "README.rst"
|
@@ -17,14 +17,15 @@ classifiers = [
|
|
17
17
|
]
|
18
18
|
[tool.poetry.dependencies]
|
19
19
|
python = "^3.8"
|
20
|
+
#
|
20
21
|
ciscoconfparse = "^1.9"
|
21
|
-
netports = "
|
22
|
+
netports = ">=0.12.1,<1.0.0"
|
22
23
|
pydantic = "^2"
|
23
24
|
pynetbox = "^7.3.3"
|
24
25
|
requests = "^2"
|
25
|
-
tomli = "2.0.1"
|
26
|
-
vhelpers = "^0.1"
|
27
26
|
tabulate = "^0.9.0"
|
27
|
+
tomli = "2.0.1"
|
28
|
+
vhelpers = ">=0.1,<1.0.0"
|
28
29
|
|
29
30
|
[tool.poetry.group.dev.dependencies]
|
30
31
|
mypy = "^1.6.1"
|
@@ -55,10 +56,11 @@ test = ["pytest"]
|
|
55
56
|
|
56
57
|
[tool.poetry.urls]
|
57
58
|
"Bug Tracker" = "https://github.com/vladimirs-git/nbforager/issues"
|
58
|
-
"Download URL" = "https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.2.
|
59
|
+
"Download URL" = "https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.2.6.tar.gz"
|
59
60
|
|
60
61
|
[tool.pylint]
|
61
62
|
max-line-length = 100
|
63
|
+
max-locals = 20
|
62
64
|
disable = "fixme"
|
63
65
|
|
64
66
|
#[tool.pylint.message_control]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|