nbforager 0.2.6__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.
Files changed (56) hide show
  1. {nbforager-0.2.6 → nbforager-0.4.1}/PKG-INFO +17 -4
  2. {nbforager-0.2.6 → nbforager-0.4.1}/README.rst +13 -0
  3. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/base_c.py +13 -8
  4. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/connector.py +14 -12
  5. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/forager.py +65 -43
  6. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/joiner.py +67 -72
  7. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/helpers.py +209 -27
  8. nbforager-0.4.1/nbforager/nb_api.py +367 -0
  9. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/nb_forager.py +23 -8
  10. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/nb_tree.py +45 -2
  11. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/nb_custom.py +1 -1
  12. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/nb_parser.py +7 -13
  13. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/types_.py +4 -1
  14. {nbforager-0.2.6 → nbforager-0.4.1}/pyproject.toml +18 -14
  15. nbforager-0.2.6/nbforager/models/__init__.py +0 -1
  16. nbforager-0.2.6/nbforager/models/dcim/__init__.py +0 -1
  17. nbforager-0.2.6/nbforager/models/dcim/interfaces/__init__.py +0 -1
  18. nbforager-0.2.6/nbforager/models/dcim/interfaces/interface_nb.py +0 -13
  19. nbforager-0.2.6/nbforager/nb_api.py +0 -187
  20. /nbforager-0.2.6/LICENCE.txt → /nbforager-0.4.1/LICENSE.txt +0 -0
  21. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/__init__.py +0 -0
  22. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/__init__.py +0 -0
  23. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/base_ca.py +0 -0
  24. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/circuits.py +0 -0
  25. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/core.py +0 -0
  26. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/dcim.py +0 -0
  27. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/extended_get.py +0 -0
  28. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/extras.py +0 -0
  29. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/ip_addresses.py +0 -0
  30. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/ipam.py +0 -0
  31. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/plugins_ca.py +0 -0
  32. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/status.py +0 -0
  33. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/tenancy.py +0 -0
  34. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/users.py +0 -0
  35. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/virtualization.py +0 -0
  36. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/api/wireless.py +0 -0
  37. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/exceptions.py +0 -0
  38. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/__init__.py +0 -0
  39. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/base_fa.py +0 -0
  40. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/circuits.py +0 -0
  41. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/core.py +0 -0
  42. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/dcim.py +0 -0
  43. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/extras.py +0 -0
  44. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/ipam.py +0 -0
  45. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/ipv4.py +0 -0
  46. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/tenancy.py +0 -0
  47. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/users.py +0 -0
  48. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/virtualization.py +0 -0
  49. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/foragers/wireless.py +0 -0
  50. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/log.py +0 -0
  51. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/messages.py +0 -0
  52. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/nb_cache.py +0 -0
  53. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/__init__.py +0 -0
  54. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/parser/nb_value.py +0 -0
  55. {nbforager-0.2.6 → nbforager-0.4.1}/nbforager/py.typed +0 -0
  56. {nbforager-0.2.6 → 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.2.6
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.12.1,<1.0.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,<1.0.0)
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.2.6.tar.gz
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
 
@@ -157,9 +157,10 @@ class BaseC:
157
157
  session timeout reached. Default is `10`.
158
158
 
159
159
  :param bool strict: When querying objects by tag, if there are no tags present,
160
- the Netbox API response returns a status_code=400. True - ConnectionError is
161
- raised when status_code=400. False - a warning message is logged and an
162
- empty list is returned with status_code=200. Default is `False`.
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`.
163
164
 
164
165
  :param bool extended_get: True - Extend filtering parameters in GET request,
165
166
  ``{parameter}`` can be used instead of ``{parameter}_id``. Default is `True`.
@@ -180,9 +181,9 @@ class BaseC:
180
181
  self.threads: int = _init_threads(**kwargs)
181
182
  self.interval: float = float(kwargs.get("interval") or 0.0)
182
183
  # Errors processing
183
- self.timeout: float = float(kwargs.get("timeout") or 60)
184
+ self.timeout: int = int(kwargs.get("timeout") or 60)
184
185
  self.max_retries: int = int(kwargs.get("max_retries") or 0)
185
- self.sleep: float = float(kwargs.get("sleep") or 10)
186
+ self.sleep: int = int(kwargs.get("sleep") or 10)
186
187
  self.strict: bool = bool(kwargs.get("strict"))
187
188
  # Settings
188
189
  self.extended_get: bool = bool(kwargs.get("extended_get"))
@@ -316,6 +317,9 @@ class BaseC:
316
317
  :return: Netbox objects. Update self _results.
317
318
  """
318
319
  offset = 0
320
+ if offsets := params_d.get("offset"):
321
+ offset = int(offsets[0])
322
+
319
323
  max_limit: int = self._set_limit(params_d)
320
324
  params_l: LParam = vparam.from_dict(params_d)
321
325
 
@@ -351,12 +355,12 @@ class BaseC:
351
355
  :return: Max limit value, update params_d["limit"] value
352
356
  """
353
357
  limit = 0
354
- if limit_ := vdict.pop("limit", params_d) or []:
358
+ if limit_ := vdict.pop(params_d, key="limit") or []:
355
359
  limit = int(limit_[0])
356
360
  if not limit:
357
361
  limit = self.limit
358
362
  max_limit = 0
359
- if max_limit_ := vdict.pop("max_limit", params_d) or []:
363
+ if max_limit_ := vdict.pop(params_d, key="max_limit") or []:
360
364
  max_limit = int(max_limit_[0])
361
365
  if max_limit and max_limit < limit:
362
366
  limit = max_limit
@@ -485,7 +489,7 @@ class BaseC:
485
489
  url=url,
486
490
  headers=self._headers(),
487
491
  verify=self.verify,
488
- timeout=self.timeout,
492
+ timeout=float(self.timeout),
489
493
  )
490
494
  except ReadTimeout:
491
495
  attempts = f"{counter} of {self.max_retries}"
@@ -501,6 +505,7 @@ class BaseC:
501
505
 
502
506
  if response.ok:
503
507
  return response
508
+
504
509
  msg = self._msg_status_code(response)
505
510
  if self._is_status_code_5xx(response):
506
511
  raise ConnectionError(f"Netbox server error: {msg}.")
@@ -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 of new object to create.
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: Data of new object to create.
48
+ :param kwargs: Parameters for creating a new object.
49
49
 
50
- :return: Data of newly crated object.
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(),
@@ -123,12 +125,12 @@ class Connector(BaseC):
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", kwargs)
141
+ id_ = vdict.pop(kwargs, key="id")
140
142
  if not id_:
141
- raise ValueError("id expected in the data.")
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,7 +4,6 @@
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
@@ -13,11 +12,12 @@ from urllib.parse import urlparse, parse_qs
13
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, missed_urls
18
- from nbforager.parser.nb_parser import find_objects
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, DiAny
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
- nb_objects: LDAny = self._get_root_data_from_netbox(**kwargs)
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.split_url(url)
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 find_objects(objects=list(self.root_d.values()), **kwargs)
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 find_objects(objects=list(self.tree_d.values()), **kwargs)
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 find_objects(objects=list(self.tree_d.values()), **kwargs)
173
+ return nb_parser.find_objects(objects=list(self.tree_d.values()), **kwargs)
171
174
 
172
175
  # ============================= helpers ==============================
173
176
 
174
- def _get_root_data_from_netbox(self, **kwargs) -> LDAny:
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.
179
202
 
180
- :param kwargs: Filter parameters.
203
+ :param bool nested: `True` - Request base and nested objects,
204
+ `False` - Request only base objects. Default id `False`
205
+
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
- self.root_d[nb_object["id"]] = nb_object
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.split_url(s)[0]]
223
- return urls
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.split_url(url)
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
- # save
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, digit = h.split_url(data["url"])
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
- model_d[int(digit)] = data
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.
@@ -1,16 +1,15 @@
1
1
  """Joiner."""
2
2
  from operator import itemgetter
3
3
 
4
- from netports import Intf
5
4
  from vhelpers import vlist
6
5
 
7
6
  from nbforager import helpers as h
8
7
  from nbforager.api.base_c import BaseC
9
- from nbforager.foragers.forager import find_objects
10
8
  from nbforager.foragers.ipv4 import IPv4
11
9
  from nbforager.nb_tree import NbTree
10
+ from nbforager.parser import nb_parser
12
11
  from nbforager.parser.nb_value import NbValue
13
- from nbforager.types_ import LDAny, DAny, LStr, DiDAny, LInt, DiLDAny
12
+ from nbforager.types_ import LDAny, DAny, LStr, DiDAny, LInt, DiLDAny, SInt
14
13
 
15
14
 
16
15
  class Joiner:
@@ -62,7 +61,7 @@ class Joiner:
62
61
  data["_sub_prefixes"] = [] # LDAny
63
62
  data["_ip_addresses"] = [] # LDAny
64
63
 
65
- def join_dcim_devices(self, **kwargs) -> None:
64
+ def join_dcim_devices(self) -> None:
66
65
  """Create additional keys to represent dcim.devices similar to the WEB UI.
67
66
 
68
67
  In dcim.devices:
@@ -83,39 +82,33 @@ class Joiner:
83
82
 
84
83
  - ``_ip_addresses``
85
84
 
86
- :param kwargs: Filtering parameters.
87
-
88
85
  :return: None. Update NbTree object.
89
86
  """
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
+ self._join_virtual_chassis()
88
+ intf_ids: LInt = self._join_dcim_devices()
89
+ self._join_ip_addresses(intf_ids, app="dcim")
93
90
 
94
- def _join_virtual_chassis(self, **kwargs):
91
+ def _join_virtual_chassis(self):
95
92
  """Add virtual-chassis members to master devices.
96
93
 
97
- :param kwargs: Additional keyword arguments to filter devices.
98
94
  :return: None. Update data in object.
99
95
  """
100
96
  app = "dcim"
101
97
  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)}
98
+ nbf_devices: DiDAny = getattr(getattr(self.tree, app), model)
104
99
 
105
- for member_id, device_d in filtered_d.items():
100
+ for member_id, device_d in nbf_devices.items():
106
101
  if device_d["virtual_chassis"]:
107
- master_id = device_d["virtual_chassis"]["master"]["id"]
102
+ master_id: int = device_d["virtual_chassis"]["master"]["id"]
108
103
  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]
104
+ if master_d := nbf_devices.get(master_id, {}):
105
+ master_d["_vc_members"][member_id] = nbf_devices[member_id]
111
106
 
112
- def _join_dcim_devices(self, **kwargs) -> LInt:
107
+ def _join_dcim_devices(self) -> LInt:
113
108
  """Create additional key/values to represent devices similar to the WEB UI.
114
109
 
115
110
  Create key/values: _interfaces, _front_ports, _console_ports, etc.
116
111
 
117
- :param kwargs: Filtering parameters.
118
-
119
112
  :return: IDs of joined ports. Update NbTree.dcim.devices object.
120
113
  """
121
114
  # models
@@ -124,69 +117,71 @@ class Joiner:
124
117
  # noinspection PyProtectedMember
125
118
  extra_keys: LStr = BaseC._extra_keys["dcim/devices/"] # pylint: disable=W0212
126
119
  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
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)}
132
-
133
- intf_ids: LInt = [] # joined interfaces
134
-
135
- for model in models:
136
- ports_d: DiDAny = getattr(getattr(self.tree, app), model)
137
- ports: LDAny = list(ports_d.values())
138
-
139
- # sort by interface idx
140
- ports_lt = [(Intf(d["name"]), d) for d in ports]
141
- ports_lt.sort(key=itemgetter(0))
142
- ports = [dict(t[1]) for t in ports_lt]
143
-
144
- # set _interfaces, _front_ports, etc.
145
- for port_d in ports:
146
- name = port_d["name"]
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.
159
-
160
- Create key/values: _interfaces, _front_ports, _console_ports, etc.
120
+ extra_models: LStr = [s.lstrip("_") for s in extra_keys]
121
+ nbf_devices: DiDAny = getattr(getattr(self.tree, app), model)
122
+
123
+ # joined interfaces, need find assigned ip-addresses
124
+ intf_ids: SInt = set()
125
+
126
+ # set _interfaces, _front_ports, etc.
127
+ for extra_model in extra_models:
128
+ ports_d: DiDAny = getattr(getattr(self.tree, app), extra_model)
129
+ for nb_port in ports_d.values():
130
+ port_name = nb_port["name"]
131
+ device_id: int = nb_port["device"]["id"]
132
+ nb_device: DAny = nbf_devices.get(device_id, {})
133
+ extra_key = f"_{extra_model}"
134
+
135
+ # if device has been downloaded from netbox
136
+ if extra_key in nb_device:
137
+ nb_device[extra_key][port_name] = nb_port
138
+
139
+ # interface ids to assign ip_addresses
140
+ if extra_model == "interfaces":
141
+ intf_id = nb_port["id"]
142
+ intf_ids.add(intf_id)
143
+
144
+ # join virtual chassis interfaces to master
145
+ for device_id, nb_device in nbf_devices.items():
146
+ for vc_member in nb_device["_vc_members"].values():
147
+ master_id = vc_member["virtual_chassis"]["master"]["id"]
148
+ if device_id == master_id:
149
+ for intf_name, nb_intf in vc_member["_interfaces"].items():
150
+ if intf_name not in nb_device["_interfaces"]:
151
+ nb_device["_interfaces"][intf_name] = nb_intf
152
+
153
+ return sorted(intf_ids)
154
+
155
+ def _join_ip_addresses(self, intf_ids: LInt, app: str) -> None:
156
+ """Add NbTree.ipam.ip_address data to NbTree.dcim.interfaces._ip_addresses or VM.
161
157
 
162
158
  :param intf_ids: Interface IDs that was joined in device/VM.
163
159
  :param app: Application name: "dcim", "virtualization"
164
160
 
165
- :return: None. Update NbTree object.
161
+ :return: None. Update NbTree.ipam.ip_addresses.
166
162
  """
167
163
  model = "interfaces"
168
164
  object_type = "virtualization.vminterface" if app == "virtualization" else "dcim.interface"
169
165
  intfs_d: DiDAny = getattr(getattr(self.tree, app), model)
170
166
  params = {"id": intf_ids}
171
- intfs_d = {d["id"]: d for d in find_objects(objects=list(intfs_d.values()), **params)}
167
+ intfs_d = {
168
+ d["id"]: d for d in nb_parser.find_objects(objects=list(intfs_d.values()), **params)
169
+ }
172
170
 
173
171
  app = "ipam"
174
- model = "ip_addresses"
175
- _key = f"_{model}"
176
- addresses_d: DiDAny = getattr(getattr(self.tree, app), model)
177
- addresses: LDAny = list(addresses_d.values())
178
- addresses.sort(key=itemgetter("address"))
179
-
180
- for address_d in addresses:
181
- address = address_d["address"]
182
- assigned_object_id = address_d["assigned_object_id"]
183
- if not assigned_object_id:
184
- continue
185
- if address_d["assigned_object_type"] != object_type:
186
- continue
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
172
+ extra_model = "ip_addresses"
173
+ extra_key = f"_{extra_model}"
174
+ nbf_addresses: DiDAny = getattr(getattr(self.tree, app), extra_model)
175
+
176
+ for nb_addr in nbf_addresses.values():
177
+ address = nb_addr["address"]
178
+ if nb_addr["assigned_object_type"] == object_type:
179
+ if assigned_object_id := nb_addr["assigned_object_id"]:
180
+ nb_intf: DAny = intfs_d.get(assigned_object_id, {}) # pylint: disable=E1101
181
+
182
+ # if device has been downloaded from netbox
183
+ if extra_key in nb_intf:
184
+ nb_intf[extra_key][address] = nb_addr
190
185
 
191
186
  def join_ipam_ipv4(self) -> None:
192
187
  """Create additional keys to represent ipam similar to the WEB UI.