annet 3.7.0__py3-none-any.whl → 3.9.0__py3-none-any.whl

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.

Potentially problematic release.


This version of annet might be problematic. Click here for more details.

@@ -139,7 +139,7 @@ class FHRPGroup(Generic[_DeviceIPT]):
139
139
  group_id: int
140
140
  display: str
141
141
  protocol: str
142
- description: str
142
+ description: str | None
143
143
 
144
144
  name: str
145
145
  auth_type: str | None
@@ -235,7 +235,7 @@ class NetboxDevice(Entity, Generic[_InterfaceT, _DeviceIPT]):
235
235
  device_type: DeviceType
236
236
  # `device_role` deprecated since v4.0, replace in derived classes.
237
237
  tenant: Optional[Entity]
238
- platform: Optional[Entity]
238
+ platform: Optional[EntityWithSlug]
239
239
  serial: str
240
240
  asset_tag: Optional[str]
241
241
  site: Entity
@@ -21,6 +21,7 @@ def storage_factory(opts: NetboxStorageOpts) -> Storage:
21
21
  "4.0": NetboxStorageV41,
22
22
  "4.1": NetboxStorageV41,
23
23
  "4.2": NetboxStorageV42,
24
+ "4.3": NetboxStorageV42,
24
25
  }
25
26
 
26
27
  status = None
@@ -37,7 +38,8 @@ def storage_factory(opts: NetboxStorageOpts) -> Storage:
37
38
  else:
38
39
  raise ValueError(f"Unsupported version: {status.netbox_version}")
39
40
  except ClientLibraryError:
40
- raise ValueError(f"Connection error: Unable to reach Netbox at URL: {opts.url}")
41
+ raise ValueError(
42
+ f"Connection error: Unable to reach Netbox at URL: {opts.url}")
41
43
  raise Exception(f"Unsupported version: {status.netbox_version}")
42
44
 
43
45
 
@@ -20,6 +20,7 @@ class PrefixV37(Prefix):
20
20
 
21
21
  @dataclass
22
22
  class IpAddressV37(IpAddress[PrefixV37]):
23
+ role: Optional[Label] = None
23
24
  prefix: Optional[PrefixV37] = None
24
25
 
25
26
 
@@ -4,7 +4,7 @@ import warnings
4
4
  from datetime import datetime, timezone
5
5
  from annet.adapters.netbox.common.models import Entity, Interface, \
6
6
  InterfaceType, IpAddress, Label, NetboxDevice, DeviceIp, IpFamily, Prefix, \
7
- FHRPGroupAssignment, FHRPGroup
7
+ FHRPGroupAssignment, FHRPGroup, EntityWithSlug
8
8
 
9
9
 
10
10
  @dataclass
@@ -14,6 +14,7 @@ class PrefixV41(Prefix):
14
14
 
15
15
  @dataclass
16
16
  class IpAddressV41(IpAddress[PrefixV41]):
17
+ role: Optional[Label] = None
17
18
  prefix: Optional[PrefixV41] = None
18
19
 
19
20
 
@@ -52,7 +53,7 @@ class InterfaceV41(Interface[IpAddressV41, FHRPGroupAssignmentV41]):
52
53
 
53
54
  @dataclass
54
55
  class NetboxDeviceV41(NetboxDevice[InterfaceV41, DeviceIpV41]):
55
- role: Entity
56
+ role: EntityWithSlug
56
57
 
57
58
  @property
58
59
  def device_role(self):
@@ -22,6 +22,7 @@ class PrefixV42(Prefix):
22
22
 
23
23
  @dataclass
24
24
  class IpAddressV42(IpAddress[PrefixV42]):
25
+ role: Optional[Label] = None
25
26
  prefix: Optional[PrefixV42] = None
26
27
 
27
28
 
annet/annlib/jsontools.py CHANGED
@@ -4,8 +4,9 @@ import copy
4
4
  import fnmatch
5
5
  import json
6
6
  from collections.abc import Mapping, Sequence
7
+ from itertools import starmap
7
8
  from operator import itemgetter
8
- from typing import Any, Dict, List, Optional
9
+ from typing import Any, Dict, Iterable, List, Optional
9
10
 
10
11
  import jsonpatch
11
12
  import jsonpointer
@@ -16,23 +17,28 @@ def format_json(data: Any, stable: bool = False) -> str:
16
17
  return json.dumps(data, indent=4, ensure_ascii=False, sort_keys=not stable) + "\n"
17
18
 
18
19
 
19
- def _sanitize_json_pointer(pattern: str) -> str:
20
- """Replace special characters on the JsonPointer valid ones"""
21
- return pattern.replace("~", "~0").replace("/", "~1")
22
-
23
-
24
20
  def apply_json_fragment(
25
21
  old: Dict[str, Any],
26
- new_fragment: Dict[str, Any],
27
- acl: List[str],
22
+ new_fragment: Dict[str, Any], *,
23
+ acl: Sequence[str],
24
+ filters: Sequence[str] | None = None,
28
25
  ) -> Dict[str, Any]:
29
26
  """
30
27
  Replace parts of the old document with 'new_fragment' using ACL restrictions.
28
+ If `filter` is not `None`, only those parts which also matches at least one filter
29
+ from the list will be modified (updated or deleted).
31
30
  """
32
31
  full_new_config = copy.deepcopy(old)
33
32
  for acl_item in acl:
34
33
  new_pointers = _resolve_json_pointers(acl_item, new_fragment)
35
34
  old_pointers = _resolve_json_pointers(acl_item, full_new_config)
35
+ if filters is not None:
36
+ new_pointers = _apply_filters_to_json_pointers(
37
+ new_pointers, filters, content=new_fragment
38
+ )
39
+ old_pointers = _apply_filters_to_json_pointers(
40
+ old_pointers, filters, content=full_new_config
41
+ )
36
42
 
37
43
  for pointer in new_pointers:
38
44
  new_value = pointer.get(new_fragment)
@@ -102,30 +108,7 @@ def apply_patch(content: Optional[bytes], patch_bytes: bytes) -> bytes:
102
108
  return new_contents
103
109
 
104
110
 
105
- def apply_acl_filters(content: Dict[str, Any], filters: List[str]) -> Dict[str, Any]:
106
- result = {}
107
- for f in filters:
108
- filter_text = f.strip()
109
- if not filter_text:
110
- continue
111
-
112
- pointers = _resolve_json_pointers(filter_text, content)
113
- for pointer in pointers:
114
- part = pointer.get(copy.deepcopy(content))
115
-
116
- sub_tree = result
117
- for i in pointer.get_parts():
118
- if i not in sub_tree:
119
- sub_tree[i] = {}
120
- sub_tree = sub_tree[i]
121
-
122
- patch = jsonpatch.JsonPatch([{"op": "add", "path": pointer.path, "value": part}])
123
- result = patch.apply(result)
124
-
125
- return result
126
-
127
-
128
- def _resolve_json_pointers(pattern: str, content: Dict[str, Any]) -> List[jsonpointer.JsonPointer]:
111
+ def _resolve_json_pointers(pattern: str, content: dict[str, Any]) -> list[jsonpointer.JsonPointer]:
129
112
  """
130
113
  Resolve globbed json pointer pattern to a list of actual pointers, existing in the document.
131
114
 
@@ -163,7 +146,7 @@ def _resolve_json_pointers(pattern: str, content: Dict[str, Any]) -> List[jsonpo
163
146
  ]
164
147
  """
165
148
  parts = jsonpointer.JsonPointer(pattern).parts
166
- matched = [([], content)]
149
+ matched = [((), content)]
167
150
  for part in parts:
168
151
  new_matched = []
169
152
  for matched_parts, doc in matched:
@@ -179,10 +162,64 @@ def _resolve_json_pointers(pattern: str, content: Dict[str, Any]) -> List[jsonpo
179
162
  if fnmatch.fnmatchcase(str(i), part)
180
163
  ]
181
164
  for key, sub_doc in keys_and_docs:
182
- new_matched.append((matched_parts + [key], sub_doc))
165
+ new_matched.append((matched_parts + (key,), sub_doc))
183
166
  matched = new_matched
184
167
 
185
- ret: List[jsonpointer.JsonPointer] = []
168
+ ret: list[jsonpointer.JsonPointer] = []
186
169
  for matched_parts, _ in matched:
187
- ret.append(jsonpointer.JsonPointer("/" + "/".join([_sanitize_json_pointer(v) for v in matched_parts])))
170
+ ret.append(jsonpointer.JsonPointer.from_parts(matched_parts))
188
171
  return ret
172
+
173
+
174
+ def _apply_filters_to_json_pointers(
175
+ pointers: Iterable[jsonpointer.JsonPointer],
176
+ filters: Sequence[str], *,
177
+ content: Any,
178
+ ) -> list[jsonpointer.JsonPointer]:
179
+
180
+ """
181
+ Takes a list of pointers, a list of filters and a document
182
+ and returns a list of pointers that match at least one of the filters
183
+ (if necessary, pointers may be deeper than from the input).
184
+
185
+ For example, given:
186
+ pointers=["/foo", "/lorem/ipsum", "/lorem/dolor"],
187
+ filters=["/foo/b*/q*", "/lorem"],
188
+ content={
189
+ "foo": {
190
+ "bar": {
191
+ "baz": [1, 2],
192
+ "qux": [3, 4]
193
+ },
194
+ "qux": {
195
+ "baz": [5, 6]
196
+ }
197
+ },
198
+ "lorem": {
199
+ "ipsum": [7, 8],
200
+ "dolor": "sit",
201
+ "amet": "consectetur"
202
+ }
203
+ }
204
+ The function will return:
205
+ ["/foo/bar/qux", "/lorem/ipsum", "/lorem/dolor"]
206
+ """
207
+
208
+ ret: set[jsonpointer.JsonPointer] = set()
209
+ for filter_item in filters:
210
+ filter_parts = jsonpointer.JsonPointer(filter_item).parts
211
+ for pointer in pointers:
212
+ pointer_parts = pointer.parts
213
+ if not all(starmap(fnmatch.fnmatchcase, zip(pointer_parts, filter_parts))):
214
+ continue # common part not matched
215
+ if len(filter_parts) > len(pointer_parts):
216
+ # filter is deeper than data pointer
217
+ deeper_doc = pointer.resolve(content)
218
+ deeper_pattern = "".join((
219
+ f"/{jsonpointer.escape(part)}"
220
+ for part in filter_parts[len(pointer_parts):]
221
+ ))
222
+ ret.update(map(pointer.join, _resolve_json_pointers(deeper_pattern, deeper_doc)))
223
+ else:
224
+ ret.add(pointer)
225
+ return sorted(ret)
annet/bgp_models.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from collections.abc import Sequence, Iterable
2
2
  from dataclasses import dataclass, field
3
- from typing import Literal, Union, Optional
3
+ from typing import Literal, Union, Optional, Annotated
4
4
 
5
5
 
6
6
  class VidRange:
@@ -162,7 +162,8 @@ class PeerOptions:
162
162
  send_lcommunity: Optional[bool] = None
163
163
  send_extcommunity: Optional[bool] = None
164
164
  send_labeled: Optional[bool] = None
165
- import_limit: Optional[bool] = None
165
+ import_limit: Optional[int] = None
166
+ import_limit_action: Optional[str] = None
166
167
  teardown_timeout: Optional[bool] = None
167
168
  redistribute: Optional[bool] = None
168
169
  passive: Optional[bool] = None
@@ -241,7 +242,9 @@ class FamilyOptions:
241
242
  vrf_name: str = ""
242
243
  multipath: int = 0
243
244
  global_multipath: int = 0
244
- aggregate: Aggregate = field(default_factory=Aggregate)
245
+ aggregate: Aggregate = field(default_factory=Aggregate) # use `aggregates` instead
246
+ aggregates: tuple[Aggregate, ...] = ()
247
+ af_loops: Optional[int] = None
245
248
  redistributes: tuple[Redistribute, ...] = ()
246
249
  allow_default: bool = False
247
250
  aspath_relax: bool = False
@@ -276,7 +279,8 @@ class PeerGroup:
276
279
  send_lcommunity: bool = False
277
280
  send_extcommunity: bool = False
278
281
  send_labeled: bool = False
279
- import_limit: bool = False
282
+ import_limit: int = False
283
+ import_limit_action: Optional[str] = None
280
284
  teardown_timeout: bool = False
281
285
  redistribute: bool = False
282
286
  passive: bool = False
annet/gen.py CHANGED
@@ -259,38 +259,19 @@ def _old_new_per_device(ctx: OldNewDeviceContext, device: Device, filterer: Filt
259
259
 
260
260
  entire_results = res.entire_results
261
261
  json_fragment_results = res.json_fragment_results
262
- old_json_fragment_files = old_files.json_fragment_files
263
262
 
264
263
  new_files = res.new_files()
265
- new_json_fragment_files = res.new_json_fragment_files(old_json_fragment_files)
266
-
267
- filters: List[str] = []
268
- filters_text = build_filter_text(filterer, device, ctx.stdin, ctx.args, ctx.config)
269
- if filters_text:
270
- filters = filters_text.split("\n")
271
-
272
- for file_name in new_json_fragment_files:
273
- if new_json_fragment_files.get(file_name) is not None:
274
- new_json_fragment_files = _update_json_config(
275
- new_json_fragment_files,
276
- file_name,
277
- jsontools.apply_acl_filters(new_json_fragment_files[file_name][0], filters)
278
- )
279
- for file_name in old_json_fragment_files:
280
- if old_json_fragment_files.get(file_name) is not None:
281
- old_json_fragment_files[file_name] = jsontools.apply_acl_filters(old_json_fragment_files[file_name], filters)
264
+
265
+ filters = None
266
+ if filters_text := build_filter_text(filterer, device, ctx.stdin, ctx.args, ctx.config):
267
+ filters = filters_text.removesuffix("\n").split("\n")
268
+
269
+ old_json_fragment_files = old_files.json_fragment_files.copy()
270
+ new_json_fragment_files = res.new_json_fragment_files(old_json_fragment_files, filters=filters)
282
271
 
283
272
  if ctx.args.acl_safe:
284
273
  safe_new_files = res.new_files(safe=True)
285
- safe_new_json_fragment_files = res.new_json_fragment_files(old_json_fragment_files, safe=True)
286
- if filters:
287
- for file_name in safe_new_json_fragment_files:
288
- if safe_new_json_fragment_files.get(file_name):
289
- safe_new_json_fragment_files = _update_json_config(
290
- safe_new_json_fragment_files,
291
- file_name,
292
- jsontools.apply_acl_filters(safe_new_json_fragment_files[file_name][0], filters)
293
- )
274
+ safe_new_json_fragment_files = res.new_json_fragment_files(old_json_fragment_files, safe=True, filters=filters)
294
275
 
295
276
  if ctx.args.profile:
296
277
  perf = res.perf_mesures()
@@ -322,13 +303,6 @@ def _old_new_per_device(ctx: OldNewDeviceContext, device: Device, filterer: Filt
322
303
  )
323
304
 
324
305
 
325
- def _update_json_config(json_files, file_name, new_config):
326
- file = list(json_files[file_name])
327
- file[0] = new_config
328
- json_files[file_name] = tuple(file)
329
- return json_files
330
-
331
-
332
306
  @dataclasses.dataclass
333
307
  class DeviceDownloadedFiles:
334
308
  # map file path to file content for entire generators
@@ -4,9 +4,11 @@ import textwrap
4
4
  from collections import OrderedDict as odict
5
5
  from typing import (
6
6
  Any,
7
+ Callable,
7
8
  Dict,
8
9
  Optional,
9
- Tuple, Callable,
10
+ Sequence,
11
+ Tuple,
10
12
  )
11
13
 
12
14
  from annet.annlib import jsontools
@@ -84,6 +86,7 @@ class RunGeneratorResult:
84
86
  self,
85
87
  old_files: Dict[str, Optional[str]],
86
88
  safe: bool = False,
89
+ filters: Sequence[str] | None = None,
87
90
  ) -> Dict[str, Tuple[Any, Optional[str]]]:
88
91
  # TODO: safe
89
92
  files: Dict[str, Tuple[Any, Optional[str]]] = {}
@@ -101,9 +104,9 @@ class RunGeneratorResult:
101
104
  previous_config: Dict[str, Any] = files[filepath][0]
102
105
  new_fragment = generator_result.config
103
106
  new_config = jsontools.apply_json_fragment(
104
- previous_config,
105
- new_fragment,
106
- result_acl,
107
+ previous_config, new_fragment,
108
+ acl=result_acl,
109
+ filters=filters,
107
110
  )
108
111
  if jsontools.format_json(new_config) == jsontools.format_json(previous_config):
109
112
  # config is not changed, deprioritize reload_cmd
@@ -1,11 +1,11 @@
1
1
  from typing import Annotated, Optional, Union
2
2
 
3
- from annet.bgp_models import Family, Redistribute
3
+ from annet.bgp_models import Family, Redistribute, Aggregate
4
4
  from .basemodel import BaseMeshModel, Concat, DictMerge, Merge, KeyDefaultDict
5
5
  from .peer_models import MeshPeerGroup
6
6
 
7
7
 
8
- class Aggregate(BaseMeshModel):
8
+ class _Aggregate(BaseMeshModel):
9
9
  policy: str
10
10
  routes: Annotated[tuple[str, ...], Concat()]
11
11
  as_path: str
@@ -17,13 +17,15 @@ class Aggregate(BaseMeshModel):
17
17
 
18
18
  class FamilyOptions(BaseMeshModel):
19
19
  def __init__(self, **kwargs):
20
- kwargs.setdefault("aggregate", Aggregate())
20
+ kwargs.setdefault("aggregate", _Aggregate())
21
21
  super().__init__(**kwargs)
22
22
  family: Family
23
23
  vrf_name: str
24
24
  multipath: int
25
25
  global_multipath: int
26
- aggregate: Annotated[Aggregate, Merge()]
26
+ aggregate: Annotated[_Aggregate, Merge()] # use `aggregates` instead
27
+ aggregates: Annotated[tuple[Aggregate, ...], Concat()]
28
+ af_loops: int
27
29
  redistributes: Annotated[tuple[Redistribute, ...], Concat()]
28
30
  allow_default: bool
29
31
  aspath_relax: bool
annet/mesh/executor.py CHANGED
@@ -22,11 +22,34 @@ from .registry import (
22
22
  logger = getLogger(__name__)
23
23
 
24
24
 
25
+ @dataclass(frozen=True)
26
+ class TargetInterface:
27
+ subif: int | None = None
28
+ svi: int | None = None
29
+ lag: int | None = None
30
+ port: str | None = None
31
+
32
+
25
33
  @dataclass(frozen=True)
26
34
  class PeerKey:
27
35
  fqdn: str
28
36
  addr: str
29
37
  vrf: str
38
+ interface: TargetInterface
39
+
40
+
41
+ def target_interface(
42
+ peer: DirectPeerDTO | IndirectPeerDTO | VirtualLocalDTO,
43
+ ports: list[str],
44
+ ) -> TargetInterface:
45
+ subif = getattr(peer, "subif", None)
46
+ svi = getattr(peer, "svi", None)
47
+ lag = getattr(peer, "lag", None)
48
+ port = None
49
+ if not (svi or lag):
50
+ if len(ports) == 1:
51
+ port = ports[0]
52
+ return TargetInterface(subif, svi, lag, port)
30
53
 
31
54
 
32
55
  class Pair(BaseMeshModel):
@@ -171,7 +194,8 @@ class MeshExecutor:
171
194
  peer_key = PeerKey(
172
195
  fqdn=pair.device.fqdn,
173
196
  addr=addr,
174
- vrf=getattr(pair.connected, "vrf", "")
197
+ vrf=getattr(pair.connected, "vrf", ""),
198
+ interface=target_interface(pair.local, [p[0] for p in ports]),
175
199
  )
176
200
  try:
177
201
  if peer_key in neighbor_peers:
@@ -278,7 +302,8 @@ class MeshExecutor:
278
302
  peer_key = PeerKey(
279
303
  fqdn=connected_device.fqdn,
280
304
  addr=addr,
281
- vrf=getattr(connected_dto, "vrf", "")
305
+ vrf=getattr(connected_dto, "vrf", ""),
306
+ interface=target_interface(pair.local, []),
282
307
  )
283
308
  try:
284
309
  if peer_key in connected_peers:
annet/mesh/peer_models.py CHANGED
@@ -45,7 +45,8 @@ class _OptionsDTO(_SharedOptionsDTO):
45
45
  extended_next_hop: bool
46
46
  send_lcommunity: bool
47
47
  send_extcommunity: bool
48
- import_limit: bool
48
+ import_limit: Optional[int]
49
+ import_limit_action: Optional[str]
49
50
  teardown_timeout: bool
50
51
  redistribute: bool
51
52
  passive: bool
@@ -4,7 +4,9 @@
4
4
  # обратной, но занимает место между прямыми там, где указано.
5
5
 
6
6
  # Фичи должны быть включены прежде всего
7
- feature
7
+ feature %order_reverse
8
+ ntp enable
9
+ ntp server
8
10
  # Далее нужно будет указать команды и их порядок
9
11
  sflow
10
12
  ip prefix-list
@@ -11,6 +11,8 @@
11
11
  # Сделано в основном для того чтобы генерировать специальные команды для наливки
12
12
  # -----
13
13
  # Physical
14
+ feature ~
15
+ ntp ~
14
16
  sflow
15
17
  aaa group server tacacs\+ *
16
18
  ~ %global
@@ -20,7 +22,7 @@ ipv6 prefix-list *
20
22
  ~ %global
21
23
  route-map ~
22
24
  ~ %global
23
- interface */(ce|xe|po|eth|vlan1\.)[0-9\/]+$/ %logic=common.permanent %diff_logic=b4com.iface.diff
25
+ interface */(ce|xe|po|eth(-0-)?|vlan1\.)[0-9\/]+$/ %logic=common.permanent %diff_logic=b4com.iface.diff
24
26
  description
25
27
  ip vrf forwarding *
26
28
  ip address *
@@ -71,7 +71,11 @@ ntp
71
71
  acl port-pool
72
72
  acl ip-pool
73
73
  acl ipv6-pool
74
- acl
74
+ acl ~
75
+ undo rule * description %order_reverse
76
+ undo rule ~ %order_reverse
77
+ rule * */deny|permit/
78
+ rule * description
75
79
 
76
80
  system tcam
77
81
 
@@ -113,13 +113,11 @@ acl ip-pool *
113
113
  acl ipv6-pool *
114
114
  ~ %global
115
115
 
116
- acl name *
116
+ acl */(name|number)/ *
117
+ rule * description
117
118
  rule * %logic=huawei.misc.undo_redo
118
- acl number *
119
- rule * %logic=huawei.misc.undo_redo
120
- acl ipv6 name *
121
- rule * %logic=huawei.misc.undo_redo
122
- acl ipv6 number *
119
+ acl ipv6 */(name|number)/ *
120
+ rule * description
123
121
  rule * %logic=huawei.misc.undo_redo
124
122
 
125
123
  */(ftp|FTP|ssh|telnet)/ server acl
@@ -141,6 +139,7 @@ ssh server key-exchange
141
139
  ssh server dh-exchange
142
140
  ssh server cipher
143
141
  ssh server hmac
142
+ ssh server publickey
144
143
 
145
144
  ftp client source %logic=huawei.misc.undo_redo
146
145
 
@@ -429,6 +428,12 @@ isis *
429
428
 
430
429
  error-down auto-recovery cause link-flap
431
430
 
431
+ configuration file auto-save backup-to-server server
432
+ configuration file auto-save
433
+
434
+
435
+ set save-configuration backup-to-server *
436
+ set save-configuration *
432
437
 
433
438
  ip route-static default-bfd
434
439
  ipv6 route-static default-bfd
@@ -323,6 +323,13 @@ class CiscoFormatter(BlockExitFormatter):
323
323
 
324
324
  tree = self.split_remove_spaces(text)
325
325
  for i, item in enumerate(tree):
326
+ # fix incorrect indent for "exit-address-family"
327
+ if item == " exit-address-family":
328
+ item = " exit-address-family"
329
+ # fix incorrect indent for "class-map match.*"
330
+ if i and tree[i - 1].startswith("class-map match"):
331
+ if item.startswith(" description"):
332
+ item = item[1:]
326
333
  block_exit_strings, new_indent = self._split_indent(
327
334
  item, additional_indent, block_exit_strings
328
335
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: annet
3
- Version: 3.7.0
3
+ Version: 3.9.0
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -21,7 +21,7 @@ Requires-Dist: yarl>=1.8.2
21
21
  Requires-Dist: adaptix==3.0.0b7
22
22
  Requires-Dist: dataclass-rest==0.4
23
23
  Provides-Extra: netbox
24
- Requires-Dist: annetbox[sync]>=0.6.0; extra == "netbox"
24
+ Requires-Dist: annetbox[sync]>=0.7.0; extra == "netbox"
25
25
  Dynamic: home-page
26
26
  Dynamic: license
27
27
  Dynamic: license-file
@@ -1,7 +1,7 @@
1
1
  annet/__init__.py,sha256=eDQub3o0HdHcTWU4ZJpySrJuffGkbWts5CHXZYwVV9A,1915
2
2
  annet/annet.py,sha256=vyQ__n5hkjub3aWO8tksHPoUSbTeK97MyMcR_U8_BSU,1016
3
3
  annet/argparse.py,sha256=v1MfhjR0B8qahza0WinmXClpR8UiDFhmwDDWtNroJPA,12855
4
- annet/bgp_models.py,sha256=n29UBUbmeO7xdY0hyMi1VxkVmeSEZH8ZzYPfZmG3U7w,12502
4
+ annet/bgp_models.py,sha256=avbt0dR9_vxNUGFJ20pJHpgQifFzCbEzZClwlQPLULY,12709
5
5
  annet/cli.py,sha256=shq3hHzrTxFL3x1_zTOR43QHo0JYs8QSwyOvGtL86Co,12733
6
6
  annet/cli_args.py,sha256=d0WuGiZfe42-nMvurX1bOb3iUMg0TSK-hxk8J1THxSY,13728
7
7
  annet/connectors.py,sha256=aoiDVLPizx8CW2p8SAwGCzyO_WW8H9xc2aujbGC4bDg,4882
@@ -10,7 +10,7 @@ annet/deploy_ui.py,sha256=XsN1i7E9cNp1SAf1YBwhEBuwN91MvlNMSrLhnQrumA8,28672
10
10
  annet/diff.py,sha256=kD_2kxz5wc2TP10xj-BHs6IPq1yNKkXxIco8czjeC6M,9497
11
11
  annet/executor.py,sha256=INlWAZFLpHurg8GTXclbSzaeSIXgZo4ccmcRulQqr88,5130
12
12
  annet/filtering.py,sha256=ZtqxPsKdV9reZoRxtQyBg22BqyMqd-2SotYcxZ-68AQ,903
13
- annet/gen.py,sha256=j6SUrhEbfVQJrF2pOuWQaaBABwABk8cYzOln78jJ320,31956
13
+ annet/gen.py,sha256=y9e7P551uJ-thjBS2p-ZRMgwQa8kPF_JAoEXJ3am77Y,30683
14
14
  annet/hardware.py,sha256=O2uadehcavZ10ssPr-db3XYHK8cpbG7C7XFkO-I6r_s,1161
15
15
  annet/implicit.py,sha256=i6UxQAQESXWlIBohNuFQuSEgvdsydRzyBu1r7nrR2F4,6083
16
16
  annet/lib.py,sha256=4N4X6jCCrig5rk7Ua4AofrV9zK9jhzkBq57fLsfBJjw,4812
@@ -29,12 +29,12 @@ annet/adapters/fetchers/stub/fetcher.py,sha256=FzpkIzPJBQVuNNSdXKoGzkALmIGMgo2gm
29
29
  annet/adapters/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
30
  annet/adapters/file/provider.py,sha256=3hNt0QQg46SVymLQ4Bh9G4VNYyhnB7gV5yu5OiIJpZE,7307
31
31
  annet/adapters/netbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- annet/adapters/netbox/provider.py,sha256=SrxW_uBLvMTqtRiYXreL6RrZK4MpxVguF2ITnYOnVHU,2226
32
+ annet/adapters/netbox/provider.py,sha256=K6P5I2UjGZ3N3ayoUaVw10mf9lE5SD8W0kpB7TSefNA,2272
33
33
  annet/adapters/netbox/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  annet/adapters/netbox/common/adapter.py,sha256=L9xFMcYk7E7w6MH8N0qdg8h-CSP4nZzrOMFlw0_33EU,2600
35
35
  annet/adapters/netbox/common/client.py,sha256=PaxHG4W9H8_uunIwMBNYkLq4eQJYoO6p6gY-ciQs7Nc,2563
36
36
  annet/adapters/netbox/common/manufacturer.py,sha256=9jTfzwx5XmETrjSbIJu_FhNaByaUbGQE787c5rBor-4,1137
37
- annet/adapters/netbox/common/models.py,sha256=dC2jej8t6XR-AY8K_qzbYRq46Urvqefp_x1O-uAy4hk,8910
37
+ annet/adapters/netbox/common/models.py,sha256=wm0aIxsv7nvQfCXoqI5sWSxRW3guLCB_aQU7KDiB0YY,8925
38
38
  annet/adapters/netbox/common/query.py,sha256=kbNQSZwkjFeDArHwA8INHUauxCxYElXtNh58pZipWdo,1867
39
39
  annet/adapters/netbox/common/status_client.py,sha256=POaqiQJ0jPcqUQH-X_fWHVnKB7TBYveNriaT0eNTlfI,769
40
40
  annet/adapters/netbox/common/storage_base.py,sha256=GVF30gV4X828MPhUOZ3HsNhWvYp0J5k6LoRFd2UOk7c,9712
@@ -43,20 +43,20 @@ annet/adapters/netbox/v24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
43
43
  annet/adapters/netbox/v24/models.py,sha256=RH2ooUPHOtHT0q1ZQE7v23FgmcsGenzzSgJyft13o1k,7605
44
44
  annet/adapters/netbox/v24/storage.py,sha256=zptzrW4aZUv_exuGw0DqQPm_kXZR3DwL4zRHYL6twTk,6219
45
45
  annet/adapters/netbox/v37/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
- annet/adapters/netbox/v37/models.py,sha256=um19-ZHC700a7vpUiW8XMwBjxPcRe1StuchAxA3wZjY,1907
46
+ annet/adapters/netbox/v37/models.py,sha256=LTjotRcTYWUR3KFvdg3LjBhek4OQblOvJr5yvP9AAPg,1940
47
47
  annet/adapters/netbox/v37/storage.py,sha256=XBYuDvNWngofXf7Kiy-V-AyGXfIszP8UQyf1N0Obq5c,5041
48
48
  annet/adapters/netbox/v41/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- annet/adapters/netbox/v41/models.py,sha256=iZUP0ca3t-hF_DBBJsp1JOlZEJk4CJkWsB4NxPJtrpA,2140
49
+ annet/adapters/netbox/v41/models.py,sha256=RI8QHpNIcrXNubis1rUFJ1j9bL881fz4meLXvAY8Yi0,2197
50
50
  annet/adapters/netbox/v41/storage.py,sha256=kK_oyWNa_Ha2vSk66ca4Uxq9OZsep3y2XsRWrLp_L-Q,5095
51
51
  annet/adapters/netbox/v42/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- annet/adapters/netbox/v42/models.py,sha256=t8UaSfzl_8ri10VoIK1TSwjL8VRmMpJYQeS3AH01VSs,2021
52
+ annet/adapters/netbox/v42/models.py,sha256=JNrBMeZ1SznjRzaqKNBP5jtHEKHN40X1V-BlgrMpKwE,2054
53
53
  annet/adapters/netbox/v42/storage.py,sha256=zScnvOqjPuS39vwXcE-QDkCUosBr-nafDdgFA4jzioY,6022
54
54
  annet/annlib/__init__.py,sha256=fT1l4xV5fqqg8HPw9HqmZVN2qwS8i6X1aIm2zGDjxKY,252
55
55
  annet/annlib/command.py,sha256=GsgzQ8oFiHFan1Tfyo340rWgfhyU4_sCKJGhBm2jG6Y,1258
56
56
  annet/annlib/diff.py,sha256=MZ6eQAU3cadQp8KaSE6uAYFtcfMDCIe_eNuVROnYkCk,4496
57
57
  annet/annlib/errors.py,sha256=jBcSFzY6Vj-FxR__vqjFm-87AwYQ0xHuAopTirii5AU,287
58
58
  annet/annlib/filter_acl.py,sha256=ZJvNpSwE5MzJS_sKLenQpZgTuM-IrngwcbukQRq90do,7195
59
- annet/annlib/jsontools.py,sha256=pz6dJMEN_TSkzWoBTzC6jp7xd3K86yVTw5KsxkuTIWQ,5747
59
+ annet/annlib/jsontools.py,sha256=XsQuaDZtBn55A-Mf6hK7BbsExa6CeyQl_m1QueI_5oE,7176
60
60
  annet/annlib/lib.py,sha256=lxmYrbeablFMhFtvFmADVpVShSFi9TN4gNWaBs_Ygm0,14952
61
61
  annet/annlib/output.py,sha256=_SjJ6G6bejvnTKqNHw6xeio0FT9oO3OIkLaOC3cEga4,7569
62
62
  annet/annlib/patching.py,sha256=1bxlLDRx15GjB6aNk6kAx7t3WPPLHffYvSy7VLGnCig,21506
@@ -87,16 +87,16 @@ annet/generators/jsonfragment.py,sha256=Cl43t9_OtNWRxesk3B69h60KvD37tiK9W7sLLmpp
87
87
  annet/generators/partial.py,sha256=XI01KDA--XwjSEU33SOQCCJZRXFq5boRz1uJA8lVA1g,3502
88
88
  annet/generators/perf.py,sha256=IaAcfEVtX7UNO11VOCXzp-FPj_tOx_CDQ34HGThCn4c,2225
89
89
  annet/generators/ref.py,sha256=QVdeL8po1D0kBsVLOpCjFR81D8yNTk-kaQj5WUM4hng,438
90
- annet/generators/result.py,sha256=zMAvGOYQU803bGy6datZduHLgrEqK2Zba_Jcf9Qn9p0,4976
90
+ annet/generators/result.py,sha256=8fBfKQ7vWi9DlGOUFYz0y47UlCI1gTqix3riX2CgQng,5065
91
91
  annet/generators/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  annet/generators/common/initial.py,sha256=qYBxXFhyOPy34cxc6hsIXseod-lYCmmbuNHpM0uteY0,1244
93
93
  annet/mesh/__init__.py,sha256=lcgdnBIxc2MAN7Er1bcErEKPqrjWO4uIp_1FldMXTYg,557
94
94
  annet/mesh/basemodel.py,sha256=E6NTOneiMDwB1NCpjDRECoaeQ0f3n_fmTLnKTrSHTU4,4917
95
- annet/mesh/device_models.py,sha256=Vt9yOYBn3b67_Lz0-xnwe4QP-0gCDkX2XvhaZ1JizOM,4710
96
- annet/mesh/executor.py,sha256=U-3LlAkd34SUD-xZK2K0KaQNy0b2xnVGFkTWlftJXWs,17235
95
+ annet/mesh/device_models.py,sha256=0qWyNlk6ICviWs4pt0XJK2wptwdwM3UwxjyNHe56CjU,4829
96
+ annet/mesh/executor.py,sha256=gfRQXSTivL0DJrhnGH-4kmCTiIxDS_vReQyle3bF2FI,17970
97
97
  annet/mesh/match_args.py,sha256=CR3kdIV9NGtyk9E2JbcOQ3TRuYEryTWP3m2yCo2VCWg,5751
98
98
  annet/mesh/models_converter.py,sha256=nCtzFR3s65MVkCE_xJm1Ci5c2hUwsr6ajfBiSmy3r_8,3565
99
- annet/mesh/peer_models.py,sha256=otemIgQCOpUy4kJ5pu5t_H1tjTSv6DoabEqBkUx7hWM,2748
99
+ annet/mesh/peer_models.py,sha256=9fx-JiLTJ6E-CVBmu0BpkJwH4JwpdTOozJYlY2uH_Q4,2796
100
100
  annet/mesh/port_processor.py,sha256=RHiMS5W8qoDkTKiarQ748bcr8bNx4g_R4Y4vZg2k4TU,478
101
101
  annet/mesh/registry.py,sha256=xmWF7yxWXmwqX2_jyMAKrbGd2G9sjb4rYDx4Xk61QKc,9607
102
102
  annet/rpl/__init__.py,sha256=8nSiFpXH4OhzRGKr-013nHwwKk5Y50uh2gL7d_IoV8U,757
@@ -154,14 +154,14 @@ annet/rulebook/texts/aruba.deploy,sha256=hI432Bq-of_LMXuUflCu7eNSEFpx6qmj0KItEw6
154
154
  annet/rulebook/texts/aruba.order,sha256=ZMakkn0EJ9zomgY6VssoptJImrHrUmYnCqivzLBFTRo,1158
155
155
  annet/rulebook/texts/aruba.rul,sha256=zvGVpoYyJvMoL0fb1NQ8we_GCLZXno8nwWpZIOScLQQ,2584
156
156
  annet/rulebook/texts/b4com.deploy,sha256=SVX8-yLHM90tJC4M-ekpGuGM1aQZW3euSGyg67l--R0,781
157
- annet/rulebook/texts/b4com.order,sha256=k_9qPnc3olAUTrAeefLwe91rUK9hYgkp8hFl9LRU0V0,932
158
- annet/rulebook/texts/b4com.rul,sha256=G64FljT5wjCVpejbNmCebfCSGo-YIUrlHrpcn3GdXHc,1409
157
+ annet/rulebook/texts/b4com.order,sha256=4Uoq0Of33L-BHdKMBNf4P4Lus_g4TwEAX5hoOD8-ey0,969
158
+ annet/rulebook/texts/b4com.rul,sha256=WAhAV5HEotXtQU0CVyS3UScDsnCjQ2qDVNdC87s3vIg,1431
159
159
  annet/rulebook/texts/cisco.deploy,sha256=Hu0NkcGv3f1CWUrnbzI3eQOPXJxtH4NNOPRV68IrW4U,1226
160
160
  annet/rulebook/texts/cisco.order,sha256=DarNICBBAEXR8VOFV8SNnuQmwGAG-TGwXlMWpFMTCDE,1927
161
161
  annet/rulebook/texts/cisco.rul,sha256=cX9CyqKpgpr8B1Qe3aEe66wZpfaGg7fmsIsmgW6Yip4,3619
162
162
  annet/rulebook/texts/huawei.deploy,sha256=5vrvhai2BjaJ793A3EHHCthic3dnGKp2_rAgViVIouI,10951
163
- annet/rulebook/texts/huawei.order,sha256=XuIaP2CpgxR0v8sIniY9DjVE_LR71tLBRENNjdQvCoY,11119
164
- annet/rulebook/texts/huawei.rul,sha256=-O597iFqiYpuo3yrfvIUbE0IulabStqGkBS-Drl3KrE,13574
163
+ annet/rulebook/texts/huawei.order,sha256=4qXuoqfJ8Hck11LACKwaWP2ARHwPrtLfCut1BDRAg7E,11244
164
+ annet/rulebook/texts/huawei.rul,sha256=-Da3qjDaAWeW1IFMsLmAzo8XTCkqc2PGTqPGV8v3btA,13705
165
165
  annet/rulebook/texts/iosxr.deploy,sha256=Hu0NkcGv3f1CWUrnbzI3eQOPXJxtH4NNOPRV68IrW4U,1226
166
166
  annet/rulebook/texts/iosxr.order,sha256=gUp6XHwzqkDsArCUAwtx3rR1qlGfYsHy2vP9oZN2oDk,1922
167
167
  annet/rulebook/texts/iosxr.rul,sha256=JMFJ94ORNeDQo_X73iPS6pFUmXYTBuL5pkUypgHcOig,2966
@@ -184,7 +184,7 @@ annet/rulebook/texts/routeros.rul,sha256=ipfxjj0mjFef6IsUlupqx4BY_Je_OUb8u_U1019
184
184
  annet/vendors/__init__.py,sha256=gQcDFlKeWDZB6vxJ_MdPWEoE-C5dg-YgXvgGkuV9YLw,569
185
185
  annet/vendors/base.py,sha256=AmM3--gqC-Rpw5Xu_-hqthWZ9EoZRL8x6eOHwadZGbo,1145
186
186
  annet/vendors/registry.py,sha256=LgPg4oxWrgxsfpLpJ6OWEGFmUzlVlHzziSXsZTi82uc,2540
187
- annet/vendors/tabparser.py,sha256=4_20MlPTMFT_epKX5UZ7AKk1XjPH6xePykyLVwjlEIE,32007
187
+ annet/vendors/tabparser.py,sha256=EQrJb0Tz0Lj5byJ8Qr5fayYUCgKDgpJcAz4Smb2kxvs,32374
188
188
  annet/vendors/library/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
189
  annet/vendors/library/arista.py,sha256=J4ltZ7sS_TgIECg2U7fizvxwfS4-s35Of0tNDNWWYbE,1302
190
190
  annet/vendors/library/aruba.py,sha256=tvrSFSA43n0uelCv-NLQnqxO01d0y2mrfhncpOX7zoQ,1257
@@ -200,8 +200,8 @@ annet/vendors/library/optixtrans.py,sha256=VdME69Ca4HAEgoaKN21fZxnmmsqqaxOe_HZja
200
200
  annet/vendors/library/pc.py,sha256=vfv31_NPi7M-4AUDL89UcpawK2E6xvCpELA209cd1ho,1086
201
201
  annet/vendors/library/ribbon.py,sha256=DDOBq-_-FL9dCxqXs2inEWZ-pvw-dJ-A-prA7cKMhec,1216
202
202
  annet/vendors/library/routeros.py,sha256=iQa7m_4wjuvcgBOI9gyZwlw1BvzJfOkvUbyoEk-NI9I,1254
203
- annet-3.7.0.dist-info/licenses/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
204
- annet-3.7.0.dist-info/licenses/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
203
+ annet-3.9.0.dist-info/licenses/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
204
+ annet-3.9.0.dist-info/licenses/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
205
205
  annet_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
206
206
  annet_generators/example/__init__.py,sha256=OJ77uj8axc-FIyIu_Xdcnzmde3oQW5mk5qbODkhuVc8,355
207
207
  annet_generators/example/hostname.py,sha256=RloLzNVetEoWPLITzfJ13Nk3CC0yi-cZB1RTd6dnuhI,2541
@@ -214,8 +214,8 @@ annet_generators/rpl_example/generator.py,sha256=EWah19gOH8G-QyNyWqxCqdRi0BK7GbM
214
214
  annet_generators/rpl_example/items.py,sha256=HPgxScDvSqJPdz0c2SppDrH82DZYC4zUaniQwcWmh4A,1176
215
215
  annet_generators/rpl_example/mesh.py,sha256=z_WgfDZZ4xnyh3cSf75igyH09hGvtexEVwy1gCD_DzA,288
216
216
  annet_generators/rpl_example/route_policy.py,sha256=z6nPb0VDeQtKD1NIg9sFvmUxBD5tVs2frfNIuKdM-5c,2318
217
- annet-3.7.0.dist-info/METADATA,sha256=onj4p2T-4pNuRQvnGTBqOmqPtPezXhWBrUWjqz6sn4c,815
218
- annet-3.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
219
- annet-3.7.0.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
220
- annet-3.7.0.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
221
- annet-3.7.0.dist-info/RECORD,,
217
+ annet-3.9.0.dist-info/METADATA,sha256=IT0giJ4lQCykA668X_GV_hDfSQv8bkpNkEOFhNXgRWI,815
218
+ annet-3.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
219
+ annet-3.9.0.dist-info/entry_points.txt,sha256=5lIaDGlGi3l6QQ2ry2jZaqViP5Lvt8AmsegdD0Uznck,192
220
+ annet-3.9.0.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
221
+ annet-3.9.0.dist-info/RECORD,,
File without changes