nbforager 0.2.2__tar.gz → 0.4.1__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.4.1}/PKG-INFO +17 -4
- {nbforager-0.2.2 → nbforager-0.4.1}/README.rst +13 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/__init__.py +3 -1
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/base_c.py +28 -15
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/connector.py +15 -13
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/forager.py +66 -78
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/joiner.py +91 -77
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/helpers.py +223 -28
- nbforager-0.4.1/nbforager/nb_api.py +367 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/nb_cache.py +4 -6
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/nb_forager.py +26 -10
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/nb_tree.py +68 -2
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/nb_custom.py +1 -1
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/nb_parser.py +45 -14
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/types_.py +4 -1
- {nbforager-0.2.2 → nbforager-0.4.1}/pyproject.toml +21 -15
- nbforager-0.2.2/nbforager/nb_api.py +0 -187
- /nbforager-0.2.2/LICENCE.txt → /nbforager-0.4.1/LICENSE.txt +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/__init__.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/base_ca.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/circuits.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/core.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/dcim.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/extended_get.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/extras.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/ip_addresses.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/ipam.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/plugins_ca.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/status.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/tenancy.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/users.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/virtualization.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/wireless.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/exceptions.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/__init__.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/base_fa.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/circuits.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/core.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/dcim.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/extras.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/ipam.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/ipv4.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/tenancy.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/users.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/virtualization.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/wireless.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/log.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/messages.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/__init__.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/nb_value.py +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/py.typed +0 -0
- {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/py_tree.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nbforager
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.1
|
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,18 +20,25 @@ 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.
|
23
|
+
Requires-Dist: netports (>=0.14,<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
|
29
|
+
Requires-Dist: vhelpers (>=0.3,<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.
|
31
|
+
Project-URL: Download URL, https://github.com/vladimirs-git/nbforager/archive/refs/tags/0.4.1.tar.gz
|
32
32
|
Project-URL: Repository, https://github.com/vladimirs-git/nbforager
|
33
33
|
Description-Content-Type: text/x-rst
|
34
34
|
|
35
|
+
|
36
|
+
.. image:: https://img.shields.io/pypi/v/netports.svg
|
37
|
+
:target: https://pypi.python.org/pypi/netports
|
38
|
+
.. image:: https://img.shields.io/badge/Python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue.svg
|
39
|
+
:target: https://pypi.python.org/pypi/logger-color
|
40
|
+
|
41
|
+
|
35
42
|
nbforager
|
36
43
|
=========
|
37
44
|
|
@@ -51,6 +58,12 @@ Fully documented on `Read the Docs`_.
|
|
51
58
|
|
52
59
|
----------------------------------------------------------------------------------------
|
53
60
|
|
61
|
+
Requirements
|
62
|
+
============
|
63
|
+
|
64
|
+
Python >=3.8,<3.12
|
65
|
+
|
66
|
+
|
54
67
|
Quickstart
|
55
68
|
==========
|
56
69
|
|
@@ -1,3 +1,10 @@
|
|
1
|
+
|
2
|
+
.. image:: https://img.shields.io/pypi/v/netports.svg
|
3
|
+
:target: https://pypi.python.org/pypi/netports
|
4
|
+
.. image:: https://img.shields.io/badge/Python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue.svg
|
5
|
+
:target: https://pypi.python.org/pypi/logger-color
|
6
|
+
|
7
|
+
|
1
8
|
nbforager
|
2
9
|
=========
|
3
10
|
|
@@ -17,6 +24,12 @@ Fully documented on `Read the Docs`_.
|
|
17
24
|
|
18
25
|
----------------------------------------------------------------------------------------
|
19
26
|
|
27
|
+
Requirements
|
28
|
+
============
|
29
|
+
|
30
|
+
Python >=3.8,<3.12
|
31
|
+
|
32
|
+
|
20
33
|
Quickstart
|
21
34
|
==========
|
22
35
|
|
@@ -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.
|
@@ -149,9 +157,10 @@ class BaseC:
|
|
149
157
|
session timeout reached. Default is `10`.
|
150
158
|
|
151
159
|
:param bool strict: When querying objects by tag, if there are no tags present,
|
152
|
-
the Netbox API response returns a status_code=400.
|
153
|
-
raised when status_code=400.
|
154
|
-
empty list is returned with status_code=200.
|
160
|
+
the Netbox API response returns a status_code=400.
|
161
|
+
True - ConnectionError is raised when status_code=400.
|
162
|
+
False - WARNING message is logged and an empty list is returned with status_code=200.
|
163
|
+
Default is `False`.
|
155
164
|
|
156
165
|
:param bool extended_get: True - Extend filtering parameters in GET request,
|
157
166
|
``{parameter}`` can be used instead of ``{parameter}_id``. Default is `True`.
|
@@ -172,9 +181,9 @@ class BaseC:
|
|
172
181
|
self.threads: int = _init_threads(**kwargs)
|
173
182
|
self.interval: float = float(kwargs.get("interval") or 0.0)
|
174
183
|
# Errors processing
|
175
|
-
self.timeout:
|
184
|
+
self.timeout: int = int(kwargs.get("timeout") or 60)
|
176
185
|
self.max_retries: int = int(kwargs.get("max_retries") or 0)
|
177
|
-
self.sleep:
|
186
|
+
self.sleep: int = int(kwargs.get("sleep") or 10)
|
178
187
|
self.strict: bool = bool(kwargs.get("strict"))
|
179
188
|
# Settings
|
180
189
|
self.extended_get: bool = bool(kwargs.get("extended_get"))
|
@@ -308,6 +317,9 @@ class BaseC:
|
|
308
317
|
:return: Netbox objects. Update self _results.
|
309
318
|
"""
|
310
319
|
offset = 0
|
320
|
+
if offsets := params_d.get("offset"):
|
321
|
+
offset = int(offsets[0])
|
322
|
+
|
311
323
|
max_limit: int = self._set_limit(params_d)
|
312
324
|
params_l: LParam = vparam.from_dict(params_d)
|
313
325
|
|
@@ -343,12 +355,12 @@ class BaseC:
|
|
343
355
|
:return: Max limit value, update params_d["limit"] value
|
344
356
|
"""
|
345
357
|
limit = 0
|
346
|
-
if limit_ := vdict.pop("limit"
|
358
|
+
if limit_ := vdict.pop(params_d, key="limit") or []:
|
347
359
|
limit = int(limit_[0])
|
348
360
|
if not limit:
|
349
361
|
limit = self.limit
|
350
362
|
max_limit = 0
|
351
|
-
if max_limit_ := vdict.pop("max_limit"
|
363
|
+
if max_limit_ := vdict.pop(params_d, key="max_limit") or []:
|
352
364
|
max_limit = int(max_limit_[0])
|
353
365
|
if max_limit and max_limit < limit:
|
354
366
|
limit = max_limit
|
@@ -477,7 +489,7 @@ class BaseC:
|
|
477
489
|
url=url,
|
478
490
|
headers=self._headers(),
|
479
491
|
verify=self.verify,
|
480
|
-
timeout=self.timeout,
|
492
|
+
timeout=float(self.timeout),
|
481
493
|
)
|
482
494
|
except ReadTimeout:
|
483
495
|
attempts = f"{counter} of {self.max_retries}"
|
@@ -493,6 +505,7 @@ class BaseC:
|
|
493
505
|
|
494
506
|
if response.ok:
|
495
507
|
return response
|
508
|
+
|
496
509
|
msg = self._msg_status_code(response)
|
497
510
|
if self._is_status_code_5xx(response):
|
498
511
|
raise ConnectionError(f"Netbox server error: {msg}.")
|
@@ -536,8 +549,8 @@ class BaseC:
|
|
536
549
|
}
|
537
550
|
return headers
|
538
551
|
|
539
|
-
def
|
540
|
-
"""Check if
|
552
|
+
def _check_extra_keys(self, items: LDAny) -> None:
|
553
|
+
"""Check if extra keys are present in the data.
|
541
554
|
|
542
555
|
The Netbox REST API returns the object as a dictionary.
|
543
556
|
NbForager inject extra key/value pairs into Netbox object.
|
@@ -548,10 +561,10 @@ class BaseC:
|
|
548
561
|
"""
|
549
562
|
for data in items:
|
550
563
|
url_o: ParseResult = urllib.parse.urlparse(data["url"])
|
551
|
-
for path,
|
564
|
+
for path, extra_keys in self._extra_keys.items():
|
552
565
|
if url_o.path.find(path) > -1:
|
553
|
-
if keys := set(
|
554
|
-
raise NbApiError(f"NbForager
|
566
|
+
if keys := set(extra_keys).intersection(data):
|
567
|
+
raise NbApiError(f"NbForager extra {keys=} detected in {self.url}.")
|
555
568
|
|
556
569
|
def _slice_params_counters(self, results: LDAny) -> LDAny:
|
557
570
|
"""Generate sliced parameters based on counts in results.
|
@@ -23,9 +23,9 @@ class Connector(BaseC):
|
|
23
23
|
# ============================= methods ==============================
|
24
24
|
|
25
25
|
def create(self, **kwargs) -> Response:
|
26
|
-
"""Create object in Netbox.
|
26
|
+
"""Create an object in Netbox.
|
27
27
|
|
28
|
-
:param kwargs: Parameters
|
28
|
+
:param kwargs: Parameters for creating a new object.
|
29
29
|
|
30
30
|
:return: Session response.
|
31
31
|
|
@@ -43,11 +43,11 @@ class Connector(BaseC):
|
|
43
43
|
return response
|
44
44
|
|
45
45
|
def create_d(self, **kwargs) -> DAny:
|
46
|
-
"""Create object in Netbox.
|
46
|
+
"""Create an object in Netbox.
|
47
47
|
|
48
|
-
:param kwargs:
|
48
|
+
:param kwargs: Parameters for creating a new object.
|
49
49
|
|
50
|
-
:return: Data of newly
|
50
|
+
:return: Data of newly created object.
|
51
51
|
:rtype: dict
|
52
52
|
"""
|
53
53
|
response: Response = self.create(**kwargs)
|
@@ -59,7 +59,7 @@ class Connector(BaseC):
|
|
59
59
|
|
60
60
|
# noinspection PyShadowingBuiltins
|
61
61
|
def delete(self, id: int) -> Response: # pylint: disable=redefined-builtin
|
62
|
-
"""Delete object in Netbox.
|
62
|
+
"""Delete an object in Netbox.
|
63
63
|
|
64
64
|
:param id: Object ID.
|
65
65
|
:type id: int
|
@@ -70,6 +70,8 @@ class Connector(BaseC):
|
|
70
70
|
- <Response [404]> Object not found.
|
71
71
|
:rtype: Response
|
72
72
|
"""
|
73
|
+
if not id:
|
74
|
+
raise ValueError("id is required.")
|
73
75
|
response: Response = self._session.delete(
|
74
76
|
url=f"{self.url}{id}",
|
75
77
|
headers=self._headers(),
|
@@ -118,17 +120,17 @@ class Connector(BaseC):
|
|
118
120
|
"""
|
119
121
|
params_ld: LDList = self._validate_params(**kwargs)
|
120
122
|
items: LDAny = self._query_params_ld(params_ld)
|
121
|
-
self.
|
123
|
+
self._check_extra_keys(items=items)
|
122
124
|
return items
|
123
125
|
|
124
126
|
# noinspection PyIncorrectDocstring
|
125
127
|
def update(self, **kwargs) -> Response:
|
126
|
-
"""Update object in Netbox.
|
128
|
+
"""Update an object in Netbox.
|
127
129
|
|
128
130
|
:param id: Netbox object id to update.
|
129
131
|
:type id: int
|
130
132
|
|
131
|
-
:param kwargs: Parameters to update object in Netbox.
|
133
|
+
:param kwargs: Parameters to update an object in Netbox.
|
132
134
|
|
133
135
|
:return: Session response.
|
134
136
|
|
@@ -136,9 +138,9 @@ class Connector(BaseC):
|
|
136
138
|
- <Response [400]> Invalid data.
|
137
139
|
:rtype: Response
|
138
140
|
"""
|
139
|
-
id_ = vdict.pop("id"
|
141
|
+
id_ = vdict.pop(kwargs, key="id")
|
140
142
|
if not id_:
|
141
|
-
raise ValueError("id
|
143
|
+
raise ValueError("id is required in the data.")
|
142
144
|
|
143
145
|
response: Response = self._session.patch(
|
144
146
|
url=f"{self.url}{id_}/",
|
@@ -151,12 +153,12 @@ class Connector(BaseC):
|
|
151
153
|
|
152
154
|
# noinspection PyIncorrectDocstring
|
153
155
|
def update_d(self, **kwargs) -> DAny:
|
154
|
-
"""Update object in Netbox.
|
156
|
+
"""Update an object in Netbox.
|
155
157
|
|
156
158
|
:param id: Netbox object id to update.
|
157
159
|
:type id: int
|
158
160
|
|
159
|
-
:param kwargs: Parameters to update object in Netbox.
|
161
|
+
:param kwargs: Parameters to update an object in Netbox.
|
160
162
|
|
161
163
|
:return: Data of updated object.
|
162
164
|
:rtype: dict
|
@@ -4,20 +4,20 @@
|
|
4
4
|
|
5
5
|
from __future__ import annotations
|
6
6
|
|
7
|
-
import logging
|
8
7
|
import time
|
9
8
|
from queue import Queue
|
10
9
|
from threading import Thread
|
11
10
|
from urllib.parse import urlparse, parse_qs
|
12
11
|
|
13
|
-
from vhelpers import
|
12
|
+
from vhelpers import vstr
|
14
13
|
|
15
14
|
from nbforager import helpers as h
|
15
|
+
from nbforager import nb_tree
|
16
16
|
from nbforager.nb_api import NbApi
|
17
|
-
from nbforager.nb_tree import NbTree
|
18
|
-
from nbforager.parser
|
17
|
+
from nbforager.nb_tree import NbTree
|
18
|
+
from nbforager.parser import nb_parser
|
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, SInt, DAny
|
21
21
|
|
22
22
|
|
23
23
|
class Forager:
|
@@ -86,7 +86,10 @@ class Forager:
|
|
86
86
|
:return: None. Update self object.
|
87
87
|
"""
|
88
88
|
# Query main data
|
89
|
-
|
89
|
+
kwargs_: DAny = self._delete_existing_nested_ids(**kwargs)
|
90
|
+
if kwargs and not kwargs_:
|
91
|
+
return
|
92
|
+
nb_objects: LDAny = self._get_root_data_from_netbox(nested=nested, **kwargs_)
|
90
93
|
if not nested:
|
91
94
|
return
|
92
95
|
urls: LStr = self._collect_nested_urls(nb_objects)
|
@@ -104,7 +107,7 @@ class Forager:
|
|
104
107
|
# loop
|
105
108
|
else:
|
106
109
|
for url in urls:
|
107
|
-
app, model, _ = h.
|
110
|
+
app, model, _ = h.url_to_ami_items(url)
|
108
111
|
path = f"{app}/{model}/"
|
109
112
|
connector = self._get_connector(path)
|
110
113
|
params_d: DList = parse_qs(urlparse(url).query)
|
@@ -134,7 +137,7 @@ class Forager:
|
|
134
137
|
|
135
138
|
:return: Filtered Netbox objects.
|
136
139
|
"""
|
137
|
-
return
|
140
|
+
return nb_parser.find_objects(objects=list(self.root_d.values()), **kwargs)
|
138
141
|
|
139
142
|
def find_rse(self, role: str = "", site: str = "", env: str = "", **kwargs) -> LDAny:
|
140
143
|
"""Find Netbox objects in NbForager.tree by Role-Sile-Env finding parameters.
|
@@ -155,7 +158,7 @@ class Forager:
|
|
155
158
|
}
|
156
159
|
params = {k: v for k, v in params.items() if v}
|
157
160
|
kwargs.update(params)
|
158
|
-
return
|
161
|
+
return nb_parser.find_objects(objects=list(self.tree_d.values()), **kwargs)
|
159
162
|
|
160
163
|
def find_tree(self, **kwargs) -> LDAny:
|
161
164
|
"""Find Netbox objects in NbForager.tree by extended finding parameters.
|
@@ -167,48 +170,50 @@ class Forager:
|
|
167
170
|
|
168
171
|
:return: Filtered Netbox objects.
|
169
172
|
"""
|
170
|
-
return
|
173
|
+
return nb_parser.find_objects(objects=list(self.tree_d.values()), **kwargs)
|
171
174
|
|
172
175
|
# ============================= helpers ==============================
|
173
176
|
|
174
|
-
def
|
177
|
+
def _delete_existing_nested_ids(self, **kwargs) -> DAny:
|
178
|
+
"""Delete the IDs of objects that are already present in the tree and nested=True.
|
179
|
+
|
180
|
+
Delete only if kwargs["id"] is a list, ignore other data types.
|
181
|
+
|
182
|
+
:param kwargs: Filtering parameters.
|
183
|
+
|
184
|
+
:return: None. Update IDs in kwargs.
|
185
|
+
"""
|
186
|
+
if list(kwargs) != ["id"]:
|
187
|
+
return kwargs
|
188
|
+
if not isinstance(kwargs["id"], list):
|
189
|
+
return kwargs
|
190
|
+
ids: SInt = set(kwargs["id"])
|
191
|
+
existing_ids: SInt = {d["id"] for d in self.find_root(_nested=True, **kwargs)}
|
192
|
+
if new_ids := sorted(set(ids).difference(existing_ids)):
|
193
|
+
return {"id": new_ids}
|
194
|
+
return {}
|
195
|
+
|
196
|
+
def _get_root_data_from_netbox(self, nested: bool = False, **kwargs) -> LDAny:
|
175
197
|
"""Retrieve data from the Netbox.
|
176
198
|
|
177
199
|
Request data based on the kwargs filter parameters and
|
178
200
|
save the received objects to the NbForager.root.
|
201
|
+
Set extra `_nested` value in Netbox object.
|
202
|
+
|
203
|
+
:param bool nested: `True` - Request base and nested objects,
|
204
|
+
`False` - Request only base objects. Default id `False`
|
179
205
|
|
180
|
-
:param kwargs:
|
206
|
+
:param kwargs: Filtering parameters.
|
181
207
|
|
182
208
|
:return: List of Netbox objects. Update NbForager.root object.
|
183
209
|
"""
|
184
210
|
nb_objects: LDAny = self.connector.get(**kwargs)
|
185
|
-
nb_objects = self._validate_ids(nb_objects)
|
186
211
|
for nb_object in nb_objects:
|
187
|
-
|
212
|
+
nb_object["_nested"] = nested
|
213
|
+
idx = int(nb_object["id"])
|
214
|
+
self.root_d[idx] = nb_object
|
188
215
|
return nb_objects
|
189
216
|
|
190
|
-
@staticmethod
|
191
|
-
def _validate_ids(nb_objects: LDAny) -> LDAny:
|
192
|
-
"""Check the IDs in the items. The ID should be a unique integer.
|
193
|
-
|
194
|
-
:param nb_objects: List of dictionaries containing Netbox objects.
|
195
|
-
|
196
|
-
:return: None. Logging an error if the ID does not match the conditions.
|
197
|
-
"""
|
198
|
-
nb_objects_: LDAny = []
|
199
|
-
for nb_object in nb_objects:
|
200
|
-
id_ = nb_object.get("id")
|
201
|
-
if not isinstance(id_, int):
|
202
|
-
msg = f"TypeError: {id_=} {int} expected in {nb_object=}."
|
203
|
-
logging.error(msg)
|
204
|
-
continue
|
205
|
-
if id_ in nb_objects:
|
206
|
-
msg = f"ValueError: Duplicate {id_=} in nb_objects_ and {nb_object=}."
|
207
|
-
logging.error(msg)
|
208
|
-
continue
|
209
|
-
nb_objects_.append(nb_object)
|
210
|
-
return nb_objects_
|
211
|
-
|
212
217
|
def _collect_nested_urls(self, nb_objects: LDAny) -> LStr:
|
213
218
|
"""Collect nested urls.
|
214
219
|
|
@@ -217,10 +222,20 @@ class Forager:
|
|
217
222
|
:return: Nested URLs.
|
218
223
|
"""
|
219
224
|
urls: LStr = h.nested_urls(nb_objects)
|
220
|
-
urls = missed_urls(urls=urls, tree=self.root)
|
225
|
+
urls = nb_tree.missed_urls(urls=urls, tree=self.root)
|
221
226
|
urls = h.join_urls(urls)
|
222
|
-
urls = [s for s in urls if h.
|
223
|
-
|
227
|
+
urls = [s for s in urls if h.url_to_ami_items(s)[0]]
|
228
|
+
|
229
|
+
urls_: LStr = []
|
230
|
+
|
231
|
+
for url in urls:
|
232
|
+
app, model, _ = h.url_to_ami_items(url)
|
233
|
+
path = f"{app}/{model}/"
|
234
|
+
connector = self._get_connector(path)
|
235
|
+
urls_sliced = h.slice_url(url, max_len=connector.url_length)
|
236
|
+
urls_.extend(urls_sliced)
|
237
|
+
|
238
|
+
return urls_
|
224
239
|
|
225
240
|
# noinspection PyProtectedMember
|
226
241
|
def _get_path_params(self, urls: LStr) -> LT2StrDAny:
|
@@ -232,7 +247,7 @@ class Forager:
|
|
232
247
|
"""
|
233
248
|
path_params: LT2StrDAny = []
|
234
249
|
for url in urls:
|
235
|
-
app, model, _ = h.
|
250
|
+
app, model, _ = h.url_to_ami_items(url)
|
236
251
|
path = f"{app}/{model}/"
|
237
252
|
connector = self._get_connector(path)
|
238
253
|
params_d = parse_qs(urlparse(url).query)
|
@@ -310,13 +325,20 @@ class Forager:
|
|
310
325
|
connector = getattr(getattr(self.api, app), model)
|
311
326
|
return connector
|
312
327
|
|
313
|
-
def _save_results(self, results):
|
314
|
-
|
328
|
+
def _save_results(self, results: LDAny) -> None:
|
329
|
+
"""Save Netbox objects to root NbTree object.
|
330
|
+
|
331
|
+
:param results: Data to be saved.
|
332
|
+
:return: None. root NbTree object.
|
333
|
+
"""
|
315
334
|
for data in results:
|
316
|
-
app, model,
|
335
|
+
app, model, idx_ = h.url_to_ami_items(data["url"])
|
336
|
+
idx = int(idx_)
|
317
337
|
path = f"{app}/{model}"
|
318
338
|
model_d: DiDAny = self._get_root_data(path)
|
319
|
-
|
339
|
+
if idx not in model_d:
|
340
|
+
data["_nested"] = False
|
341
|
+
model_d[idx] = data
|
320
342
|
|
321
343
|
def _get_pynb_data(self, path: str) -> DiAny:
|
322
344
|
"""Get data in self pynb by app/model path.
|
@@ -339,37 +361,3 @@ class Forager:
|
|
339
361
|
app, model = h.path_to_attrs(path)
|
340
362
|
data = getattr(getattr(self.root, app), model)
|
341
363
|
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_
|