annet 0.13.6__py3-none-any.whl → 0.14.1__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.

@@ -204,7 +204,7 @@ class NetboxStorageV37(Storage):
204
204
  res = extend_device(
205
205
  device=device,
206
206
  storage=self,
207
- interfaces=interfaces[device.id],
207
+ interfaces=interfaces,
208
208
  neighbours=neighbours,
209
209
  )
210
210
  return res
annet/annlib/jsontools.py CHANGED
@@ -28,7 +28,11 @@ def apply_json_fragment(
28
28
  try:
29
29
  new_value = pointer.get(new_fragment)
30
30
  except jsonpointer.JsonPointerException:
31
- # no value found in new_fragment by the pointer, skip the ACL item
31
+ # no value found in new_fragment by the pointer,
32
+ # try to delete it from the new config
33
+ doc, part = pointer.to_last(full_new_config)
34
+ if isinstance(doc, dict) and isinstance(part, str):
35
+ doc.pop(part, None)
32
36
  continue
33
37
 
34
38
  _ensure_pointer_exists(full_new_config, pointer)
annet/annlib/patching.py CHANGED
@@ -15,8 +15,8 @@ from typing import ( # pylint: disable=unused-import
15
15
  from .lib import jun_activate, merge_dicts, strip_annotation, uniq
16
16
  from .rbparser import platform
17
17
  from .rbparser.ordering import compile_ordering_text
18
- from .rulebook.common import default as common_default
19
18
  from .rulebook.common import call_diff_logic
19
+ from .rulebook.common import default as common_default
20
20
  from .tabparser import CommonFormatter
21
21
  from .types import Diff, Op
22
22
 
@@ -115,7 +115,7 @@ class PatchTree:
115
115
  return (
116
116
  f"PatchTree(\n"
117
117
  f" itms=[\n"
118
- f" {n.join(itms).strip()}\n"
118
+ f"{textwrap.indent(n.join(itms), ' ')}\n"
119
119
  f" ]\n"
120
120
  f")"
121
121
  )
annet/annlib/tabparser.py CHANGED
@@ -1,9 +1,14 @@
1
+ import dataclasses
1
2
  import itertools
2
3
  import re
3
4
  from collections import OrderedDict as odict
5
+ from typing import TYPE_CHECKING, Any, Dict, Iterable, Optional, Tuple, Union
4
6
 
5
7
  from .types import Op
6
8
 
9
+ if TYPE_CHECKING:
10
+ from .patching import PatchTree
11
+
7
12
 
8
13
  # =====
9
14
  class ParserError(Exception):
@@ -23,6 +28,40 @@ class BlockEnd:
23
28
  pass
24
29
 
25
30
 
31
+ RowWithContext = Tuple[str, Optional[Dict[str, Any]]]
32
+
33
+
34
+ def block_wrapper(value: Any) -> Iterable[Any]:
35
+ yield from iter((BlockBegin, value, BlockEnd))
36
+
37
+
38
+ @dataclasses.dataclass
39
+ class FormatterContext:
40
+ parent: Optional["FormatterContext"] = None
41
+
42
+ prev: Optional[RowWithContext] = None
43
+ current: Optional[RowWithContext] = None
44
+ next: Optional[RowWithContext] = None
45
+
46
+ @property
47
+ def level(self) -> int:
48
+ if self.parent is None:
49
+ return 0
50
+ return self.parent.level + 1
51
+
52
+ @property
53
+ def row_prev(self) -> Optional[str]:
54
+ return self.prev and self.prev[0]
55
+
56
+ @property
57
+ def row(self) -> Optional[str]:
58
+ return self.current and self.current[0]
59
+
60
+ @property
61
+ def row_next(self) -> Optional[str]:
62
+ return self.next and self.next[0]
63
+
64
+
26
65
  # =====
27
66
  class CommonFormatter:
28
67
  def __init__(self, indent=" "):
@@ -41,12 +80,12 @@ class CommonFormatter:
41
80
  )
42
81
  )
43
82
 
44
- def diff(self, diff):
45
- return list(self._diff_lines(diff))
46
-
47
83
  def diff_generator(self, diff):
48
84
  yield from self._diff_lines(diff)
49
85
 
86
+ def diff(self, diff):
87
+ return list(self.diff_generator(diff))
88
+
50
89
  def patch(self, patch):
51
90
  return "\n".join(
52
91
  _filtered_block_marks(
@@ -103,17 +142,37 @@ class CommonFormatter:
103
142
  row = self._indent * _level + row
104
143
  yield row
105
144
 
106
- def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
107
- empty_context = {}
145
+ def blocks_and_context(
146
+ self,
147
+ tree: "PatchTree",
148
+ is_patch: bool,
149
+ context: Optional[FormatterContext] = None
150
+ ):
151
+ if context is None:
152
+ context = FormatterContext()
153
+
108
154
  if is_patch:
109
- items = ((item.row, item.child, item.context) for item in tree.itms)
155
+ items = [(item.row, item.child, item.context) for item in tree.itms]
110
156
  else:
111
- items = ((row, child, empty_context) for row, child in tree.items())
112
- for row, sub_config, context in items:
113
- yield row, context
157
+ items = [(row, child, {}) for row, child in tree.items()]
158
+
159
+ n = len(items)
160
+ for i in range(n):
161
+ prev_row, prev_sub_config, prev_row_context = items[i - 1] if i > 0 else (None, None, None)
162
+ row, sub_config, row_context = items[i]
163
+ next_row, next_sub_config, next_row_context = items[i + 1] if i + 1 < n else (None, None, None)
164
+
165
+ context.current = (row, row_context)
166
+ context.prev = (prev_row, prev_row_context) if prev_row else None
167
+ context.next = (next_row, next_row_context) if next_row else None
168
+
169
+ yield row, row_context
170
+
114
171
  if sub_config or (is_patch and sub_config is not None):
115
172
  yield BlockBegin, None
116
- yield from self.blocks_and_context(sub_config, is_patch, _level + 1, row)
173
+ yield from self.blocks_and_context(
174
+ sub_config, is_patch, context=FormatterContext(parent=context)
175
+ )
117
176
  yield BlockEnd, None
118
177
 
119
178
  def _blocks(self, tree, is_patch):
@@ -133,20 +192,32 @@ class BlockExitFormatter(CommonFormatter):
133
192
  res = super().split(text)
134
193
  return res
135
194
 
136
- def block_exit(self, parent):
137
- if not (parent and parent.startswith(self._no_block_exit)):
138
- return self._block_exit
195
+ def block_exit(self, context: Optional[FormatterContext]) -> Iterable[Any]:
196
+ current = context and context.row
197
+ if current and not current.startswith(self._no_block_exit):
198
+ yield from block_wrapper(self._block_exit)
199
+
200
+ def blocks_and_context(self, tree, is_patch, context: Optional[FormatterContext] = None):
201
+ if context is None:
202
+ context = FormatterContext()
203
+
204
+ level = context.level
205
+ block_level = level
139
206
 
140
- def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
141
- last_context = {}
142
- for row, context in super().blocks_and_context(tree, is_patch, _level, _parent):
143
- yield row, context
144
- if context is not None:
145
- last_context = context
146
- if is_patch and _level > 0:
147
- exit_statement = self.block_exit(_parent)
148
- if exit_statement:
149
- yield exit_statement, last_context
207
+ last_row_context = {}
208
+ for row, row_context in super().blocks_and_context(tree, is_patch, context=context):
209
+ yield row, row_context
210
+ if row_context is not None:
211
+ last_row_context = row_context
212
+
213
+ if row is BlockBegin:
214
+ block_level += 1
215
+ elif row is BlockEnd:
216
+ block_level -= 1
217
+
218
+ if row is BlockEnd and block_level == level and is_patch:
219
+ for exit_statement in filter(None, self.block_exit(context)):
220
+ yield exit_statement, last_row_context
150
221
 
151
222
 
152
223
  class HuaweiFormatter(BlockExitFormatter):
@@ -166,18 +237,30 @@ class HuaweiFormatter(BlockExitFormatter):
166
237
  # например на VRP V100R006C00SPC500 + V100R006SPH003
167
238
  policy_end_blocks = ("end-list", "endif", "end-filter")
168
239
  tree = self.split_remove_spaces(text)
169
- tree[:] = filter(lambda x: not x.endswith(policy_end_blocks), tree)
240
+ tree[:] = filter(lambda x: not str(x).strip().startswith(policy_end_blocks), tree)
170
241
  return tree
171
242
 
172
- def block_exit(self, parent):
173
- if parent:
174
- if parent.startswith("xpl route-filter"):
175
- return "end-filter"
176
- elif parent.startswith(("xpl")):
177
- return "end-list"
178
- elif parent.startswith("if") and parent.endswith("then"):
179
- return "endif"
180
- return super().block_exit(parent)
243
+ def block_exit(self, context: Optional[FormatterContext]):
244
+ row = context and context.row or ""
245
+ row_next = context and context.row_next
246
+ parent_row = context and context.parent and context.parent.row or ""
247
+
248
+ if row.startswith("xpl route-filter"):
249
+ yield from block_wrapper("end-filter")
250
+ return
251
+
252
+ if row.startswith("xpl"):
253
+ yield from block_wrapper("end-list")
254
+ return
255
+
256
+ if parent_row.startswith("xpl route-filter"):
257
+ if (row.startswith(("if", "elseif")) and row.endswith("then")) and not row_next:
258
+ yield "endif"
259
+ elif row == "else":
260
+ yield "endif"
261
+ return
262
+
263
+ yield from super().block_exit(context)
181
264
 
182
265
 
183
266
  class CiscoFormatter(BlockExitFormatter):
@@ -198,15 +281,17 @@ class AsrFormatter(BlockExitFormatter):
198
281
  tree[:] = filter(lambda x: not x.endswith(policy_end_blocks), tree)
199
282
  return tree
200
283
 
201
- def block_exit(self, parent):
202
- if parent:
203
- if parent.startswith(("prefix-set", "as-path-set", "community-set")):
204
- return "end-set"
205
- elif parent.startswith("if") and parent.endswith("then"):
206
- return "endif"
207
- elif parent.startswith("route-policy"):
208
- return "end-policy"
209
- return super().block_exit(parent)
284
+ def block_exit(self, context: Optional[FormatterContext]) -> str:
285
+ current = context and context.row or ""
286
+
287
+ if current.startswith(("prefix-set", "as-path-set", "community-set")):
288
+ yield from block_wrapper("end-set")
289
+ elif current.startswith("if") and current.endswith("then"):
290
+ yield from block_wrapper("endif")
291
+ elif current.startswith("route-policy"):
292
+ yield from block_wrapper("end-policy")
293
+ else:
294
+ yield from super().block_exit(context)
210
295
 
211
296
 
212
297
  class JuniperFormatter(CommonFormatter):
@@ -381,18 +466,24 @@ class RosFormatter(CommonFormatter):
381
466
  def patch_plain(self, patch):
382
467
  return list(self.cmd_paths(patch).keys())
383
468
 
384
- def blocks_and_context(self, tree, is_patch, _level=0, _parent=None):
469
+ def blocks_and_context(
470
+ self,
471
+ tree: "PatchTree",
472
+ is_patch: bool,
473
+ context: Optional[FormatterContext] = None
474
+ ):
385
475
  rows = []
386
- empty_context = {}
476
+
387
477
  if is_patch:
388
478
  items = ((item.row, item.child, item.context) for item in tree.itms)
389
479
  else:
390
- items = ((row, child, empty_context) for row, child in tree.items())
391
- for row, sub_config, context in items:
480
+ items = ((row, child, {}) for row, child in tree.items())
481
+
482
+ for row, sub_config, row_context in items:
392
483
  if sub_config or (is_patch and sub_config is not None):
393
- rows.append((row, sub_config, context))
484
+ rows.append((row, sub_config, row_context))
394
485
  else:
395
- rows.append((row, None, context))
486
+ rows.append((row, None, row_context))
396
487
 
397
488
  prev_prow = None
398
489
  for sub_config, row_group in itertools.groupby(rows, lambda x: x[1]):
@@ -400,20 +491,25 @@ class RosFormatter(CommonFormatter):
400
491
  if prev_prow:
401
492
  yield prev_prow
402
493
  yield BlockBegin, None
403
- for (row, _, context) in row_group:
404
- yield row, context
494
+ for row, _, row_context in row_group:
495
+ yield row, row_context
405
496
  if prev_prow:
406
497
  yield BlockEnd, None
407
498
  else:
408
- for (row, _, context) in row_group:
409
- if _parent:
410
- prev_prow = _parent
411
- prow = f"{_parent} {row}"
499
+ for row, _, row_context in row_group:
500
+ if context and context.parent and context.parent.row:
501
+ prev_prow = context.parent.row
502
+ prow = f"{context.parent.row} {row}"
412
503
  else:
413
504
  prow = row
414
- yield prow, context
505
+ yield prow, row_context
506
+
415
507
  yield BlockBegin, None
416
- yield from self.blocks_and_context(sub_config, is_patch, _level + 1, prow)
508
+ yield from self.blocks_and_context(
509
+ sub_config,
510
+ is_patch,
511
+ context=FormatterContext(parent=context, current=(prow, row_context))
512
+ )
417
513
  yield BlockEnd, None
418
514
 
419
515
  def _formatted_blocks(self, blocks):
annet/cli_args.py CHANGED
@@ -27,7 +27,7 @@ def convert_to_none(arg):
27
27
 
28
28
 
29
29
  def valid_config_source(value):
30
- if value not in ["cfglister", "running", "empty", "-"] and not os.path.exists(value):
30
+ if value not in ["running", "empty", "-"] and not os.path.exists(value):
31
31
  raise ValueError("No such file or directory %r" % value)
32
32
  return value
33
33
 
@@ -174,14 +174,14 @@ opt_annotate = Arg(
174
174
 
175
175
  opt_config = Arg(
176
176
  "--config", default="running", type=valid_config_source,
177
- help="'cfglister', 'running', 'empty', путь к файлу конфига, "
177
+ help="'running', 'empty', путь к файлу конфига, "
178
178
  "каталогу с файлами конфига в формате <hostname>.cfg "
179
179
  "или '-' (stdin)"
180
180
  )
181
181
 
182
182
  opt_clear = Arg(
183
183
  "--clear", default=False,
184
- help="Используя acl вычищает команды относящиеся к данному генератору"
184
+ help="Используя acl вычищает команды относящиеся к данному генератору, "
185
185
  "аналогично использованию return в самом начале генератора"
186
186
  )
187
187
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: annet
3
- Version: 0.13.6
3
+ Version: 0.14.1
4
4
  Summary: annet
5
5
  Home-page: https://github.com/annetutil/annet
6
6
  License: MIT
@@ -2,7 +2,7 @@ annet/__init__.py,sha256=oyElxAW97sHgQccGafhaWVBveBn1gSjjdP_xHROaRLg,2139
2
2
  annet/annet.py,sha256=TMdEuM7GJQ4TjRVmuK3bCTZN-21lxjQ9sXqEdILUuBk,725
3
3
  annet/argparse.py,sha256=MoBD0LPnHdA6HU7z1uQNArYlkD92znoeGIFTMnS4dRM,12608
4
4
  annet/cli.py,sha256=Nk956O3nOEVsOLyOqKDgjqJaB2uVlwYb9tPrWMbB4E4,9632
5
- annet/cli_args.py,sha256=Wv2X2wk6trTsGpKpcaZacGqVbDxMzEJvN8uYIJOq4D0,16071
5
+ annet/cli_args.py,sha256=5sCD20ls18NDASPOHT5MbAFB10gyayGcFosKaJTLDps,16047
6
6
  annet/connectors.py,sha256=FLFJlhb73g2c8e61zYnGn8dTkIiaaWYqeUCsZd48AWg,2471
7
7
  annet/deploy.py,sha256=B8E0P_VvCrS2URjFvgmUiIkHp95g7pAWfmT34igaDeo,18893
8
8
  annet/diff.py,sha256=zLcaCnb4lZRUb7frpH1CstQ3kacRcCblZs1uLG8J5lk,3391
@@ -34,17 +34,17 @@ annet/adapters/netbox/common/storage_opts.py,sha256=rl_0pr3VzmOy6PDZIUMkKSBfJh90
34
34
  annet/adapters/netbox/v24/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  annet/adapters/netbox/v24/storage.py,sha256=ogCu5XXIpcpPtuz7dWfDFIbqtz8k5G96PL6mhAQWwBU,6026
36
36
  annet/adapters/netbox/v37/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- annet/adapters/netbox/v37/storage.py,sha256=XIP11yp_ypi2lEQdtqNXGkzUYNxFCfDsmUM5pnoMFPU,7496
37
+ annet/adapters/netbox/v37/storage.py,sha256=3Xv4BoStxy80iev4B9WMxUxl_U0LdKG6ZoTykMpliLw,7485
38
38
  annet/annlib/__init__.py,sha256=fT1l4xV5fqqg8HPw9HqmZVN2qwS8i6X1aIm2zGDjxKY,252
39
39
  annet/annlib/command.py,sha256=uuBddMQphtn8P5MO5kzIa8_QrtMns-k05VeKv1bcAuA,1043
40
40
  annet/annlib/diff.py,sha256=UPt3kYFQdQdVVy3ePYzNHPAxMVmHxCrCnZnMCV6ou2Q,4587
41
41
  annet/annlib/errors.py,sha256=jBcSFzY6Vj-FxR__vqjFm-87AwYQ0xHuAopTirii5AU,287
42
42
  annet/annlib/filter_acl.py,sha256=0w1VF6WcONiTYTQh0yWi6_j9rCTc_kMLAUMr0hbdkNU,7203
43
- annet/annlib/jsontools.py,sha256=UXZrO6RZ4LIcJkQ6xUcef6WuHMgpMTipolcIBZTEhuc,3461
43
+ annet/annlib/jsontools.py,sha256=aGf1ZmkyuQ7FOB7-sP4KGE4smLd7pDZHBUGEF_XQTso,3651
44
44
  annet/annlib/lib.py,sha256=eJ4hcVuQ6pdYBzLs4YKCHFtq45idOfZCYp92XfF7_QI,15317
45
45
  annet/annlib/output.py,sha256=_SjJ6G6bejvnTKqNHw6xeio0FT9oO3OIkLaOC3cEga4,7569
46
- annet/annlib/patching.py,sha256=Gh8uUjFyYND9TJBBQH-DH6-AwFiiR-dXVXOisMS7elg,19784
47
- annet/annlib/tabparser.py,sha256=bhYbuGJIg6VP5CLiq09UZjgrenP9DegKvw_kYy-Hus0,22910
46
+ annet/annlib/patching.py,sha256=GwG2lT4w-I1Ls3Eswn6SPJLBUVt2M3Ysw1fmXsWamYw,19793
47
+ annet/annlib/tabparser.py,sha256=oV2sR_paW_tXUfHuAtL9OtV2V4PZICQuxH4VXOjhcqM,25793
48
48
  annet/annlib/types.py,sha256=VHU0CBADfYmO0xzB_c5f-mcuU3dUumuJczQnqGlib9M,852
49
49
  annet/annlib/netdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
50
  annet/annlib/netdev/db.py,sha256=fI_u5aya4l61mbYSjj4JwlVfi3s7obt2jqERSuXGRUI,1634
@@ -124,10 +124,10 @@ annet/rulebook/texts/routeros.rul,sha256=ipfxjj0mjFef6IsUlupqx4BY_Je_OUb8u_U1019
124
124
  annet_generators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
125
125
  annet_generators/example/__init__.py,sha256=zVd8_DrXuOASrNzg2Ab94rPyvJff83L-_HppDFxnUjM,228
126
126
  annet_generators/example/lldp.py,sha256=68CLrK7vxTQQy9XIBxtywuEdBNlIlfXGYj8_wYWs5UI,1146
127
- annet-0.13.6.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
128
- annet-0.13.6.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
129
- annet-0.13.6.dist-info/METADATA,sha256=o_MGhpo38KbBhndlGER9EnhCvJdLWy1uZbsDNr78ODU,694
130
- annet-0.13.6.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
131
- annet-0.13.6.dist-info/entry_points.txt,sha256=yHimujIzR2bwNIbb--MTs_GpXiAve89Egpu2LlgTEkg,119
132
- annet-0.13.6.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
133
- annet-0.13.6.dist-info/RECORD,,
127
+ annet-0.14.1.dist-info/AUTHORS,sha256=rh3w5P6gEgqmuC-bw-HB68vBCr-yIBFhVL0PG4hguLs,878
128
+ annet-0.14.1.dist-info/LICENSE,sha256=yPxl7dno02Pw7gAcFPIFONzx_gapwDoPXsIsh6Y7lC0,1079
129
+ annet-0.14.1.dist-info/METADATA,sha256=Ytr-6UkYSrc_wLkYHOza5V7iSPFGHtdteNJ6IoQzdzw,694
130
+ annet-0.14.1.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
131
+ annet-0.14.1.dist-info/entry_points.txt,sha256=yHimujIzR2bwNIbb--MTs_GpXiAve89Egpu2LlgTEkg,119
132
+ annet-0.14.1.dist-info/top_level.txt,sha256=QsoTZBsUtwp_FEcmRwuN8QITBmLOZFqjssRfKilGbP8,23
133
+ annet-0.14.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.1)
2
+ Generator: setuptools (70.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5