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.
Files changed (55) hide show
  1. {nbforager-0.2.2 → nbforager-0.2.6}/PKG-INFO +4 -4
  2. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/__init__.py +3 -1
  3. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/base_c.py +15 -7
  4. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/connector.py +1 -1
  5. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/forager.py +6 -40
  6. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/joiner.py +64 -45
  7. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/helpers.py +14 -1
  8. nbforager-0.2.6/nbforager/models/__init__.py +1 -0
  9. nbforager-0.2.6/nbforager/models/dcim/__init__.py +1 -0
  10. nbforager-0.2.6/nbforager/models/dcim/interfaces/__init__.py +1 -0
  11. nbforager-0.2.6/nbforager/models/dcim/interfaces/interface_nb.py +13 -0
  12. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_cache.py +4 -6
  13. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_forager.py +3 -2
  14. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_tree.py +23 -0
  15. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/nb_parser.py +39 -2
  16. {nbforager-0.2.2 → nbforager-0.2.6}/pyproject.toml +7 -5
  17. {nbforager-0.2.2 → nbforager-0.2.6}/LICENCE.txt +0 -0
  18. {nbforager-0.2.2 → nbforager-0.2.6}/README.rst +0 -0
  19. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/__init__.py +0 -0
  20. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/base_ca.py +0 -0
  21. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/circuits.py +0 -0
  22. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/core.py +0 -0
  23. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/dcim.py +0 -0
  24. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/extended_get.py +0 -0
  25. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/extras.py +0 -0
  26. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/ip_addresses.py +0 -0
  27. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/ipam.py +0 -0
  28. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/plugins_ca.py +0 -0
  29. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/status.py +0 -0
  30. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/tenancy.py +0 -0
  31. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/users.py +0 -0
  32. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/virtualization.py +0 -0
  33. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/api/wireless.py +0 -0
  34. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/exceptions.py +0 -0
  35. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/__init__.py +0 -0
  36. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/base_fa.py +0 -0
  37. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/circuits.py +0 -0
  38. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/core.py +0 -0
  39. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/dcim.py +0 -0
  40. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/extras.py +0 -0
  41. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/ipam.py +0 -0
  42. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/ipv4.py +0 -0
  43. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/tenancy.py +0 -0
  44. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/users.py +0 -0
  45. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/virtualization.py +0 -0
  46. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/foragers/wireless.py +0 -0
  47. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/log.py +0 -0
  48. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/messages.py +0 -0
  49. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/nb_api.py +0 -0
  50. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/__init__.py +0 -0
  51. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/nb_custom.py +0 -0
  52. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/parser/nb_value.py +0 -0
  53. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/py.typed +0 -0
  54. {nbforager-0.2.2 → nbforager-0.2.6}/nbforager/py_tree.py +0 -0
  55. {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.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.13.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.2)
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.2.tar.gz
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
- _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.
@@ -536,8 +544,8 @@ class BaseC:
536
544
  }
537
545
  return headers
538
546
 
539
- def _check_reserved_keys(self, items: LDAny) -> None:
540
- """Check if reserved keys are present in the data.
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, reserved_keys in self._reserved_keys.items():
559
+ for path, extra_keys in self._extra_keys.items():
552
560
  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}.")
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._check_reserved_keys(items=items)
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 vlist, vstr
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 NbParser
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, TLists, DiAny
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 _find(objects=list(self.root_d.values()), **kwargs)
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 _find(objects=list(self.tree_d.values()), **kwargs)
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 _find(objects=list(self.tree_d.values()), **kwargs)
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.parser.nb_value import NbValue
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}/".replace("_", "-")
39
- reserved_keys: LStr = BaseC._reserved_keys[key] # pylint: disable=W0212
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 reserved_keys:
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._join_dcim_devices(app="dcim")
86
- self._join_dcim_interfaces(app="dcim")
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 join_virtualization_virtual_machines(self) -> None:
89
- """Create additional keys to represent virtualization.virtual_machines.
94
+ def _join_virtual_chassis(self, **kwargs):
95
+ """Add virtual-chassis members to master devices.
90
96
 
91
- In virtualization.virtual_machines:
92
-
93
- - ``_interfaces``
94
-
95
- In virtualization.interfaces:
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
- - ``_ip_addresses``
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
- :return: None. Update NbTree object.
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
- # noinspection PyProtectedMember
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 app: Application name: "dcim", "virtualization"
117
+ :param kwargs: Filtering parameters.
109
118
 
110
- :return: None. Update NbTree object.
119
+ :return: IDs of joined ports. Update NbTree.dcim.devices object.
111
120
  """
121
+ # models
122
+ app = "dcim"
112
123
  model = "devices"
113
- key = "dcim/devices/"
114
- if app == "virtualization":
115
- model = "virtual_machines"
116
- key = "virtualization/virtual-machines/"
117
- reserved_keys: LStr = BaseC._reserved_keys[key] # pylint: disable=W0212
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
- # set values
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[key]["id"]
138
- device_d: DAny = devices_d.get(id_, {}) # pylint: disable=E1101
139
- _key = f"_{model}"
140
- if _key in device_d:
141
- device_d[_key][name] = port_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
- def _join_dcim_interfaces(self, app: str) -> None:
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
- interfaces_d: DiDAny = getattr(getattr(self.tree, app), model)
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
- interface_d: DAny = interfaces_d.get(assigned_object_id, {}) # pylint: disable=E1101
169
- if _key in interface_d:
170
- interface_d[_key][address] = address_d
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 "_".join(model.split("-"))
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
- msg = f"Cache loaded from path={self.cache}."
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
- msg = f"{error}. Please change permissions by command: {cmd}."
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
- msg = f"Cache saved to path={self.cache}."
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.{hostname}.pickle.
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.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 = "^0.12.1"
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.2.tar.gz"
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