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.
Files changed (52) hide show
  1. {nbforager-0.2.2 → nbforager-0.4.1}/PKG-INFO +17 -4
  2. {nbforager-0.2.2 → nbforager-0.4.1}/README.rst +13 -0
  3. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/__init__.py +3 -1
  4. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/base_c.py +28 -15
  5. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/connector.py +15 -13
  6. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/forager.py +66 -78
  7. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/joiner.py +91 -77
  8. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/helpers.py +223 -28
  9. nbforager-0.4.1/nbforager/nb_api.py +367 -0
  10. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/nb_cache.py +4 -6
  11. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/nb_forager.py +26 -10
  12. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/nb_tree.py +68 -2
  13. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/nb_custom.py +1 -1
  14. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/nb_parser.py +45 -14
  15. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/types_.py +4 -1
  16. {nbforager-0.2.2 → nbforager-0.4.1}/pyproject.toml +21 -15
  17. nbforager-0.2.2/nbforager/nb_api.py +0 -187
  18. /nbforager-0.2.2/LICENCE.txt → /nbforager-0.4.1/LICENSE.txt +0 -0
  19. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/__init__.py +0 -0
  20. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/base_ca.py +0 -0
  21. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/circuits.py +0 -0
  22. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/core.py +0 -0
  23. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/dcim.py +0 -0
  24. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/extended_get.py +0 -0
  25. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/extras.py +0 -0
  26. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/ip_addresses.py +0 -0
  27. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/ipam.py +0 -0
  28. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/plugins_ca.py +0 -0
  29. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/status.py +0 -0
  30. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/tenancy.py +0 -0
  31. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/users.py +0 -0
  32. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/virtualization.py +0 -0
  33. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/api/wireless.py +0 -0
  34. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/exceptions.py +0 -0
  35. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/__init__.py +0 -0
  36. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/base_fa.py +0 -0
  37. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/circuits.py +0 -0
  38. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/core.py +0 -0
  39. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/dcim.py +0 -0
  40. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/extras.py +0 -0
  41. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/ipam.py +0 -0
  42. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/ipv4.py +0 -0
  43. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/tenancy.py +0 -0
  44. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/users.py +0 -0
  45. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/virtualization.py +0 -0
  46. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/foragers/wireless.py +0 -0
  47. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/log.py +0 -0
  48. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/messages.py +0 -0
  49. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/__init__.py +0 -0
  50. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/parser/nb_value.py +0 -0
  51. {nbforager-0.2.2 → nbforager-0.4.1}/nbforager/py.typed +0 -0
  52. {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.2.2
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,<0.13.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,<0.2)
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.2.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
 
@@ -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
- _reserved_keys: DLStr = {
73
+ _extra_keys: DLStr = {
74
74
  "ipam/": [
75
- # ipam aggregates, prefixes, ip_addresses
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. True - ConnectionError is
153
- raised when status_code=400. False - a warning message is logged and an
154
- 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`.
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: float = float(kwargs.get("timeout") or 60)
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: float = float(kwargs.get("sleep") or 10)
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", params_d) or []:
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", params_d) or []:
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 _check_reserved_keys(self, items: LDAny) -> None:
540
- """Check if reserved keys are present in the data.
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, reserved_keys in self._reserved_keys.items():
564
+ for path, extra_keys in self._extra_keys.items():
552
565
  if url_o.path.find(path) > -1:
553
- if keys := set(reserved_keys).intersection(data):
554
- raise NbApiError(f"NbForager reserved {keys=} detected in {self.url}.")
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 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(),
@@ -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._check_reserved_keys(items=items)
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", 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,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 vlist, vstr
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 NbParser
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, TLists, 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=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=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=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.
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: Filter parameters.
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.
@@ -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_