annet 0.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.

Files changed (137) hide show
  1. annet/__init__.py +61 -0
  2. annet/adapters/__init__.py +0 -0
  3. annet/adapters/netbox/__init__.py +0 -0
  4. annet/adapters/netbox/common/__init__.py +0 -0
  5. annet/adapters/netbox/common/client.py +87 -0
  6. annet/adapters/netbox/common/manufacturer.py +62 -0
  7. annet/adapters/netbox/common/models.py +105 -0
  8. annet/adapters/netbox/common/query.py +23 -0
  9. annet/adapters/netbox/common/status_client.py +25 -0
  10. annet/adapters/netbox/common/storage_opts.py +14 -0
  11. annet/adapters/netbox/provider.py +34 -0
  12. annet/adapters/netbox/v24/__init__.py +0 -0
  13. annet/adapters/netbox/v24/api_models.py +73 -0
  14. annet/adapters/netbox/v24/client.py +59 -0
  15. annet/adapters/netbox/v24/storage.py +196 -0
  16. annet/adapters/netbox/v37/__init__.py +0 -0
  17. annet/adapters/netbox/v37/api_models.py +38 -0
  18. annet/adapters/netbox/v37/client.py +62 -0
  19. annet/adapters/netbox/v37/storage.py +149 -0
  20. annet/annet.py +25 -0
  21. annet/annlib/__init__.py +7 -0
  22. annet/annlib/command.py +49 -0
  23. annet/annlib/diff.py +158 -0
  24. annet/annlib/errors.py +8 -0
  25. annet/annlib/filter_acl.py +196 -0
  26. annet/annlib/jsontools.py +116 -0
  27. annet/annlib/lib.py +495 -0
  28. annet/annlib/netdev/__init__.py +0 -0
  29. annet/annlib/netdev/db.py +62 -0
  30. annet/annlib/netdev/devdb/__init__.py +28 -0
  31. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  32. annet/annlib/netdev/views/__init__.py +0 -0
  33. annet/annlib/netdev/views/dump.py +121 -0
  34. annet/annlib/netdev/views/hardware.py +112 -0
  35. annet/annlib/output.py +246 -0
  36. annet/annlib/patching.py +533 -0
  37. annet/annlib/rbparser/__init__.py +0 -0
  38. annet/annlib/rbparser/acl.py +120 -0
  39. annet/annlib/rbparser/deploying.py +55 -0
  40. annet/annlib/rbparser/ordering.py +52 -0
  41. annet/annlib/rbparser/platform.py +51 -0
  42. annet/annlib/rbparser/syntax.py +115 -0
  43. annet/annlib/rulebook/__init__.py +0 -0
  44. annet/annlib/rulebook/common.py +350 -0
  45. annet/annlib/tabparser.py +648 -0
  46. annet/annlib/types.py +35 -0
  47. annet/api/__init__.py +826 -0
  48. annet/argparse.py +415 -0
  49. annet/cli.py +237 -0
  50. annet/cli_args.py +503 -0
  51. annet/configs/context.yml +18 -0
  52. annet/configs/logging.yaml +39 -0
  53. annet/connectors.py +77 -0
  54. annet/deploy.py +536 -0
  55. annet/diff.py +84 -0
  56. annet/executor.py +551 -0
  57. annet/filtering.py +40 -0
  58. annet/gen.py +865 -0
  59. annet/generators/__init__.py +435 -0
  60. annet/generators/base.py +136 -0
  61. annet/generators/common/__init__.py +0 -0
  62. annet/generators/common/initial.py +33 -0
  63. annet/generators/entire.py +97 -0
  64. annet/generators/exceptions.py +10 -0
  65. annet/generators/jsonfragment.py +125 -0
  66. annet/generators/partial.py +119 -0
  67. annet/generators/perf.py +79 -0
  68. annet/generators/ref.py +15 -0
  69. annet/generators/result.py +127 -0
  70. annet/hardware.py +45 -0
  71. annet/implicit.py +139 -0
  72. annet/lib.py +128 -0
  73. annet/output.py +167 -0
  74. annet/parallel.py +448 -0
  75. annet/patching.py +25 -0
  76. annet/reference.py +148 -0
  77. annet/rulebook/__init__.py +114 -0
  78. annet/rulebook/arista/__init__.py +0 -0
  79. annet/rulebook/arista/iface.py +16 -0
  80. annet/rulebook/aruba/__init__.py +16 -0
  81. annet/rulebook/aruba/ap_env.py +146 -0
  82. annet/rulebook/aruba/misc.py +8 -0
  83. annet/rulebook/cisco/__init__.py +0 -0
  84. annet/rulebook/cisco/iface.py +68 -0
  85. annet/rulebook/cisco/misc.py +57 -0
  86. annet/rulebook/cisco/vlandb.py +90 -0
  87. annet/rulebook/common.py +19 -0
  88. annet/rulebook/deploying.py +87 -0
  89. annet/rulebook/huawei/__init__.py +0 -0
  90. annet/rulebook/huawei/aaa.py +75 -0
  91. annet/rulebook/huawei/bgp.py +97 -0
  92. annet/rulebook/huawei/iface.py +33 -0
  93. annet/rulebook/huawei/misc.py +337 -0
  94. annet/rulebook/huawei/vlandb.py +115 -0
  95. annet/rulebook/juniper/__init__.py +107 -0
  96. annet/rulebook/nexus/__init__.py +0 -0
  97. annet/rulebook/nexus/iface.py +92 -0
  98. annet/rulebook/patching.py +143 -0
  99. annet/rulebook/ribbon/__init__.py +12 -0
  100. annet/rulebook/texts/arista.deploy +20 -0
  101. annet/rulebook/texts/arista.order +125 -0
  102. annet/rulebook/texts/arista.rul +59 -0
  103. annet/rulebook/texts/aruba.deploy +20 -0
  104. annet/rulebook/texts/aruba.order +83 -0
  105. annet/rulebook/texts/aruba.rul +87 -0
  106. annet/rulebook/texts/cisco.deploy +27 -0
  107. annet/rulebook/texts/cisco.order +82 -0
  108. annet/rulebook/texts/cisco.rul +105 -0
  109. annet/rulebook/texts/huawei.deploy +188 -0
  110. annet/rulebook/texts/huawei.order +388 -0
  111. annet/rulebook/texts/huawei.rul +471 -0
  112. annet/rulebook/texts/juniper.rul +120 -0
  113. annet/rulebook/texts/nexus.deploy +24 -0
  114. annet/rulebook/texts/nexus.order +85 -0
  115. annet/rulebook/texts/nexus.rul +83 -0
  116. annet/rulebook/texts/nokia.rul +31 -0
  117. annet/rulebook/texts/pc.order +5 -0
  118. annet/rulebook/texts/pc.rul +9 -0
  119. annet/rulebook/texts/ribbon.deploy +22 -0
  120. annet/rulebook/texts/ribbon.rul +77 -0
  121. annet/rulebook/texts/routeros.order +38 -0
  122. annet/rulebook/texts/routeros.rul +45 -0
  123. annet/storage.py +125 -0
  124. annet/tabparser.py +36 -0
  125. annet/text_term_format.py +95 -0
  126. annet/tracing.py +170 -0
  127. annet/types.py +227 -0
  128. annet-0.0.dist-info/AUTHORS +21 -0
  129. annet-0.0.dist-info/LICENSE +21 -0
  130. annet-0.0.dist-info/METADATA +26 -0
  131. annet-0.0.dist-info/RECORD +137 -0
  132. annet-0.0.dist-info/WHEEL +5 -0
  133. annet-0.0.dist-info/entry_points.txt +5 -0
  134. annet-0.0.dist-info/top_level.txt +2 -0
  135. annet_generators/__init__.py +0 -0
  136. annet_generators/example/__init__.py +12 -0
  137. annet_generators/example/lldp.py +53 -0
annet/annlib/lib.py ADDED
@@ -0,0 +1,495 @@
1
+ import abc
2
+ import bisect
3
+ import contextlib
4
+ import functools
5
+ import ipaddress
6
+ import itertools
7
+ import math
8
+ import os
9
+ import pathlib
10
+ import re
11
+ import socket
12
+ import sys
13
+ import textwrap
14
+ import typing
15
+ from collections import OrderedDict as odict
16
+ from collections.abc import Iterable
17
+ from functools import lru_cache
18
+ from typing import List, NamedTuple, Optional, Tuple, Union
19
+
20
+ import contextlog
21
+ import jinja2
22
+ import mako.template
23
+
24
+ _logger = contextlog.get_logger()
25
+
26
+
27
+ # =====
28
+ class HuaweiNumBlock(metaclass=abc.ABCMeta):
29
+ def __init__(self, generator, block_name, start=5, step=5):
30
+ self._gen = generator
31
+ self._block_name = block_name
32
+ self._rule_num = start
33
+ self._rule_step = step
34
+ self._block = None
35
+
36
+ def _current(self):
37
+ (current, self._rule_num) = (self._rule_num, self._rule_num + self._rule_step)
38
+ return current
39
+
40
+ def __enter__(self):
41
+ self._block = self._gen.block(self._block_name)
42
+ self._block.__enter__()
43
+ return self
44
+
45
+ def __exit__(self, exc_type, exc_val, trace):
46
+ self._block.__exit__(exc_type, exc_val, trace)
47
+ self._block = None
48
+
49
+ def __call__(self, rule_str, min_number=None):
50
+ if min_number is not None:
51
+ self._rule_num = max(self._rule_num, int(min_number))
52
+ return self.format_rule_string(rule_str)
53
+
54
+ @abc.abstractmethod
55
+ def format_rule_string(self, rule_str):
56
+ pass
57
+
58
+
59
+ def huawei_expand_vlandb(row):
60
+ expanded = set()
61
+ row_parts = row.split()
62
+ for (index, part) in enumerate(row_parts):
63
+ if part == "to":
64
+ left = int(row_parts[index - 1])
65
+ right = int(row_parts[index + 1])
66
+ expanded = expanded.union(range(left + 1, right))
67
+ else:
68
+ expanded.add(int(part))
69
+ return expanded
70
+
71
+
72
+ def cisco_expand_vlandb(row):
73
+ expanded = set()
74
+ for part in row.split(","):
75
+ range_parts = [int(p.strip()) for p in part.strip().split("-")]
76
+ assert len(range_parts) in (1, 2)
77
+ if len(range_parts) > 1:
78
+ left = range_parts[0]
79
+ right = range_parts[1]
80
+ expanded.update(range(left, right + 1))
81
+ else:
82
+ expanded.add(int(range_parts[0]))
83
+ return expanded
84
+
85
+
86
+ def huawei_collapse_vlandb(vlans, chunk_len=0):
87
+ return collapse_vlandb(vlans, " to ", chunk_len=chunk_len)
88
+
89
+
90
+ def cisco_collapse_vlandb(vlans, tiny_ranges=True):
91
+ return collapse_vlandb(vlans, "-", tiny_ranges)
92
+
93
+
94
+ def juniper_fmt_prefix_lists_acl(prefix_list_names: typing.Iterable[str]) -> str:
95
+ """Форматирует строку для acl из prefix-list-имён.
96
+
97
+ Производит следующие операции:
98
+
99
+ - вырезает дубликаты
100
+ - сортирует
101
+ - добавляет quoted-вариант имени (NOCDEV-5321)
102
+ - соединяет всё в строку
103
+ """
104
+ sorted_unique_names = sorted(frozenset(prefix_list_names))
105
+ quoted_and_unquoted_names = []
106
+ for prefix_list_name in sorted_unique_names:
107
+ quoted_and_unquoted_names.append(prefix_list_name)
108
+ quoted_and_unquoted_names.append('"{}"'.format(prefix_list_name))
109
+ joined_prefix_lists = "|".join(quoted_and_unquoted_names)
110
+ return joined_prefix_lists
111
+
112
+
113
+ # tiny_ranges - если выставлен в False, то два рядом стоящих влана не конвертятся в
114
+ # рендж (поведение cisco catalyst)
115
+ def collapse_vlandb(vlans, range_sep, tiny_ranges=True, chunk_len=0):
116
+ assert len(vlans) != 0
117
+ vlans = sorted(set(vlans))
118
+ res = []
119
+ row = [vlans[0], vlans[0]]
120
+ for vlan in vlans[1::]:
121
+ if row[1] == (vlan - 1):
122
+ row[1] = vlan
123
+ elif not tiny_ranges and row[1] - row[0] == 1:
124
+ res.append([row[0], row[0]])
125
+ res.append([row[1], row[1]])
126
+ row = [vlan, vlan]
127
+ else:
128
+ res.append(row)
129
+ row = [vlan, vlan]
130
+ res.append(row)
131
+ res = list(map(lambda x: x[0] != x[1] and "%s%s%s" % (x[0], range_sep, x[1]) or str(x[0]), res))
132
+
133
+ # Устройства бьют списки вланов на чанки при добавлении в конфиг
134
+ if chunk_len:
135
+ chunks = [res[i:i + chunk_len] for i in range(0, len(res), chunk_len)]
136
+ return chunks
137
+
138
+ return res
139
+
140
+
141
+ def huawei_iface_ranges(iface_names):
142
+ ret = []
143
+ grouped = odict()
144
+ for iface in iface_names:
145
+ match = re.match(r"(.*[:/])(\d+)$", iface)
146
+ if not match:
147
+ ret.append(iface)
148
+ else:
149
+ prefix, index = match.groups()
150
+ grouped.setdefault(prefix, []).append((int(index), iface))
151
+
152
+ for ifaces in grouped.values():
153
+ start = 0
154
+ while start < len(ifaces):
155
+ end = start
156
+ while end + 1 < len(ifaces) and ifaces[end + 1][0] == ifaces[end][0] + 1:
157
+ end += 1
158
+ if start == end:
159
+ ret.append(ifaces[start][1])
160
+ else:
161
+ ret.append((ifaces[start][1], ifaces[end][1]))
162
+ start = end + 1
163
+
164
+ return ret
165
+
166
+
167
+ def juniper_port_split(iface_name):
168
+ ret = dict(zip(["speed", "fpc", "pic", "port"], re.split(r"-+|/+|:", iface_name)))
169
+ return ret
170
+
171
+
172
+ def make_ip4_mask(prefix_len, inverse=False):
173
+ """
174
+ Returns dotted-quad string representing IPv4 mask of given length
175
+
176
+ E.g., for prefix_len=25 it returns "255.255.255.128"
177
+ """
178
+ if not isinstance(prefix_len, int) or prefix_len < 0 or prefix_len > 32:
179
+ raise ValueError("invalid prefix_len %r" % prefix_len)
180
+ bin_mask = (0xffffffff & (0xffffffff << (32 - prefix_len))).to_bytes(4, byteorder="big")
181
+ if inverse:
182
+ bin_mask = bytes(byte ^ 255 for byte in bin_mask)
183
+ return socket.inet_ntop(socket.AF_INET, bin_mask)
184
+
185
+
186
+ def merge_dicts(*args):
187
+ if len(args) == 0:
188
+ return odict()
189
+ if len(args) == 1:
190
+ return args[0]
191
+ if all(map(lambda x: x[0] == x[1], zip(args[:-1], args[1:]))):
192
+ return args[0]
193
+
194
+ merged = odict()
195
+ for dictionary in args:
196
+ assert isinstance(dictionary, (dict, odict))
197
+ for (key, value) in dictionary.items():
198
+ if isinstance(value, (dict, odict)):
199
+ merged[key] = merge_dicts(*[x[key] for x in args if key in x])
200
+ elif key not in merged:
201
+ merged[key] = value
202
+ elif isinstance(merged[key], (list, tuple)):
203
+ merged[key] = merged[key].__class__(itertools.chain(merged[key], value))
204
+ else:
205
+ merged[key] = value
206
+ return merged
207
+
208
+
209
+ def meta_decor(*method_list):
210
+ def wrapper(*args, **kwds):
211
+ for method in method_list:
212
+ res = method(*args, **kwds)
213
+ return res
214
+
215
+ return wrapper
216
+
217
+
218
+ def first(iterable, default=None):
219
+ return next(iter(iterable), default)
220
+
221
+
222
+ def flatten(iterable):
223
+ for x in iterable:
224
+ if not isinstance(x, (str, bytes)) and isinstance(x, Iterable):
225
+ yield from flatten(x)
226
+ else:
227
+ yield x
228
+
229
+
230
+ def uniq(*iterables):
231
+ seen = set()
232
+ for iterable in iterables:
233
+ for item in iterable:
234
+ if item not in seen:
235
+ seen.add(item)
236
+ yield item
237
+
238
+
239
+ # Хелпер чтобы при итерировании по вложенным структурам
240
+ # правильно подбирать значения и флаги (local_as, update_source, multipath, etc)
241
+ # которые должны наследоваться от верхних блоков к нижним
242
+ # XXX Покрыть тестами
243
+ class ContextOrderedDict:
244
+ def __init__(self, iterable):
245
+ self.dict = odict(iterable)
246
+
247
+ @contextlib.contextmanager
248
+ def update(self, peers_sub_dict):
249
+ values_backup = self.dict.copy()
250
+ for key in set(self.dict).intersection(peers_sub_dict.keys()):
251
+ if not isinstance(peers_sub_dict[key], dict):
252
+ self.dict[key] = peers_sub_dict[key]
253
+ yield
254
+ self.dict = values_backup
255
+ del values_backup
256
+
257
+
258
+ # {{{ http://code.activestate.com/recipes/511478/ (r1)
259
+ def percentile(list_numbers, percent, key=lambda x: x):
260
+ """
261
+ Find the percentile of a list of values.
262
+
263
+ @parameter N - is a list of values.
264
+ @parameter percent - a float value from 0.0 to 1.0.
265
+ @parameter key - optional key function to compute value from each element of N.
266
+
267
+ @return - the percentile of the values
268
+ """
269
+ if not list_numbers:
270
+ return None
271
+ list_numbers = sorted(list_numbers, key=key)
272
+ kkk = (len(list_numbers) - 1) * percent
273
+ floo = math.floor(kkk)
274
+ cei = math.ceil(kkk)
275
+ if floo == cei:
276
+ return key(list_numbers[int(kkk)])
277
+ d0 = key(list_numbers[int(floo)]) * (cei - kkk)
278
+ d1 = key(list_numbers[int(cei)]) * (kkk - floo)
279
+ return d0 + d1
280
+
281
+
282
+ def mako_render(template, dedent=False, **kwargs):
283
+ @lru_cache(None)
284
+ def _compile_mako(template, dedent):
285
+ if dedent:
286
+ template = textwrap.dedent(template).strip()
287
+ return mako.template.Template(template)
288
+
289
+ ret = _compile_mako(template, dedent).render(**kwargs)
290
+ if dedent:
291
+ ret = ret.strip()
292
+ return ret
293
+
294
+
295
+ def jinja_render(template, dedent=False, **kwargs):
296
+ @lru_cache(None)
297
+ def _compile_jinja(template, dedent):
298
+ if dedent:
299
+ template = textwrap.dedent(template).strip()
300
+ return jinja2.Template(template)
301
+
302
+ ret = _compile_jinja(template, dedent).render(**kwargs)
303
+ if dedent:
304
+ ret = ret.strip()
305
+ return ret
306
+
307
+
308
+ # =====
309
+ def find_exc_in_stack(
310
+ container_exc: Exception,
311
+ target_exc_type: Union[type, Tuple[type, ...]]
312
+ ) -> Optional[BaseException]:
313
+ curr_err: Optional[BaseException] = container_exc
314
+ while curr_err is not None:
315
+ if isinstance(curr_err, target_exc_type):
316
+ return curr_err
317
+ curr_err = curr_err.__context__
318
+ return None
319
+
320
+
321
+ def find_modules(base_dir):
322
+ """
323
+ Рекурсивно ищем в base_dir файлы python модулей
324
+ """
325
+ for entry in os.scandir(base_dir):
326
+ if entry.name[:1] in ["_", "."]:
327
+ continue
328
+
329
+ if entry.is_dir():
330
+ child_dir = os.path.join(base_dir, entry.name)
331
+ yield from (".".join((entry.name, child_name)) for child_name in find_modules(child_dir))
332
+ elif entry.is_file() and entry.name.endswith(".py"):
333
+ yield entry.name[:-3]
334
+
335
+
336
+ def catch_ctrl_c(func):
337
+ @functools.wraps(func)
338
+ def wrapper(*args, **kwargs):
339
+ try:
340
+ return func(*args, **kwargs)
341
+ except KeyboardInterrupt:
342
+ _logger.error("Got Ctrl+C, exiting")
343
+ sys.exit(127)
344
+
345
+ return wrapper
346
+
347
+
348
+ class LMSegment(NamedTuple):
349
+ """
350
+ LMSegment хранит подсеть в виде целочисленных пар (int(network_addr), int(broadcast_addr)):
351
+ 0.0.0.0/0: (0b0000000000000000000000000, 0b1111111111111111111111111)
352
+ 0.0.0.0/16: (0b0000000000000000000000000, 0b0000000000001111111111111)
353
+ 1.0.0.0/24: (0b1000000000000000000000000, 0b1000000000000000011111111)
354
+
355
+ Для двух x=(A, B), y=(C, D) таких пар (A <= B, C <= D по определению):
356
+
357
+ Отношение включения: x contains y <-> A <= C && D <= B:
358
+ 1.0.0.0/24 contains 1.0.0.1/32
359
+ 0.0.0.0/0 contains 0.0.0.0/16
360
+
361
+ Отношение порядка: x < y <-> A < C || (A == C && B > D), или проще (A, -B) < (C, -D):
362
+ 1.0.0.0/24 < 1.0.0.1/32
363
+ 0.0.0.0/0 < 0.0.0.0/16
364
+ """
365
+
366
+ begin: int
367
+ end: int
368
+
369
+ def contains(self, other: "LMSegment") -> bool:
370
+ return self.begin <= other.begin and other.end <= self.end
371
+
372
+ @classmethod
373
+ def from_net(cls, net: Union[ipaddress.IPv4Network, ipaddress.IPv6Network]):
374
+ return cls(
375
+ begin=int(net.network_address),
376
+ end=int(net.network_address) | int(net.hostmask),
377
+ )
378
+
379
+ def _cmp_identity(self) -> Tuple[int, int]:
380
+ return (self.begin, -self.end)
381
+
382
+ def __eq__(self, other: "LMSegment"):
383
+ return self._cmp_identity() == other._cmp_identity()
384
+
385
+ def __lt__(self, other: "LMSegment"):
386
+ return self._cmp_identity() < other._cmp_identity()
387
+
388
+ def __le__(self, other: "LMSegment"):
389
+ return self._cmp_identity() <= other._cmp_identity()
390
+
391
+ def __ge__(self, other: "LMSegment"):
392
+ return self._cmp_identity() >= other._cmp_identity()
393
+
394
+ def __gt__(self, other: "LMSegment"):
395
+ return self._cmp_identity() > other._cmp_identity()
396
+
397
+
398
+ class LMSegmentList:
399
+ """Упорядоченный список подсетей
400
+ """
401
+
402
+ def __init__(self):
403
+ self.pfxs: List[LMSegment] = []
404
+
405
+ def add(self, pref: LMSegment) -> None:
406
+ """Добавляем новый префикс в упорядоченный список, если он не дублируется
407
+ """
408
+ idx = bisect.bisect(self.pfxs, pref) - 1
409
+ if 0 <= idx < len(self.pfxs):
410
+ if pref == self.pfxs[idx]:
411
+ return
412
+ self.pfxs.insert(idx + 1, pref)
413
+
414
+ def find(self, target: LMSegment) -> Optional[LMSegment]:
415
+ """LPM поиск в добавленных
416
+ """
417
+ upper_bound = bisect.bisect(self.pfxs, target)
418
+ for i in reversed(range(0, upper_bound)):
419
+ if self.pfxs[i].contains(target):
420
+ return self.pfxs[i]
421
+ if self.pfxs[i] > target:
422
+ break
423
+ return None
424
+
425
+
426
+ class LMSMatcher:
427
+ """Обертка над парой LMSegmentList над парой LMSegmentList для v4/v6
428
+ """
429
+
430
+ def __init__(self):
431
+ self.v4 = LMSegmentList()
432
+ self.v6 = LMSegmentList()
433
+
434
+ def add(self, pref: str) -> None:
435
+ net = ipaddress.ip_network(pref)
436
+ self._af(net).add(LMSegment.from_net(net))
437
+
438
+ def find(self, pref: str) -> Optional[str]:
439
+ net = ipaddress.ip_network(pref)
440
+ found = self._af(net).find(LMSegment.from_net(net))
441
+ if found:
442
+ net_addr = net.__class__(found.begin).network_address
443
+ net_hostmask = (found.begin ^ found.end).bit_length()
444
+ net_mask = net_addr.max_prefixlen - net_hostmask
445
+ return str(net_addr) + "/" + str(net_mask)
446
+
447
+ def _af(self, net):
448
+ if net.version == 4:
449
+ return self.v4
450
+ elif net.version == 6:
451
+ return self.v6
452
+ else:
453
+ raise ValueError(str(net))
454
+
455
+
456
+ def is_relative(path: pathlib.PurePath, *other: str) -> bool:
457
+ """Проверяет является ли путь path относительным любого из other
458
+ """
459
+ for checkpath in other:
460
+ try:
461
+ path.relative_to(checkpath)
462
+ return True
463
+ except ValueError:
464
+ pass
465
+ return False
466
+
467
+
468
+ _ANNOTATION_SEP = "\t# "
469
+
470
+
471
+ def add_annotation(row: str, annotation: str) -> str:
472
+ return _ANNOTATION_SEP.join((row, annotation))
473
+
474
+
475
+ def strip_annotation(row: str) -> str:
476
+ return row.rsplit(_ANNOTATION_SEP, 1)[0]
477
+
478
+
479
+ def jun_inactive_pfx() -> str:
480
+ return "inactive: "
481
+
482
+
483
+ def jun_is_inactive(key) -> bool:
484
+ return key.startswith(jun_inactive_pfx())
485
+
486
+
487
+ def jun_activate(key) -> str:
488
+ if jun_is_inactive(key):
489
+ key = key[len(jun_inactive_pfx()):]
490
+ return key
491
+
492
+
493
+ # TODO: Remove with python3.10
494
+ async def anext(async_iter):
495
+ return await async_iter.__anext__() # pylint: disable=unnecessary-dunder-call
File without changes
@@ -0,0 +1,62 @@
1
+ import collections
2
+ import functools
3
+
4
+
5
+ def get_db(prepared):
6
+ allowed = _make_allowed_by_seq(prepared)
7
+ return (
8
+ _build_tree(prepared, allowed),
9
+ functools.reduce(set.union, allowed.values()), # All sequences
10
+ )
11
+
12
+
13
+ def find_true_sequences(hw_model, tree):
14
+ sequences = set()
15
+ for (regexp, meta) in tree.items():
16
+ if regexp.search(hw_model):
17
+ sequences.update(meta["sequences"])
18
+ sequences.update(find_true_sequences(hw_model, meta["children"]))
19
+ return sequences
20
+
21
+
22
+ def _build_tree(prepared, allowed_by_seq):
23
+ tree = {}
24
+ for (seq, regexp) in prepared.items():
25
+ sub = tree
26
+ for sub_seq in _seq_subs(seq):
27
+ regexp = prepared[sub_seq]
28
+ if regexp not in sub:
29
+ sub[regexp] = {
30
+ "sequences": allowed_by_seq[sub_seq],
31
+ "children": {},
32
+ }
33
+ sub = sub[regexp]["children"]
34
+ return tree
35
+
36
+
37
+ def _seq_subs(seq):
38
+ for index in range(1, len(seq) + 1):
39
+ yield seq[:index]
40
+
41
+
42
+ def _make_allowed_by_seq(sequences):
43
+ all_variants = collections.Counter()
44
+ variants_by_seq = {}
45
+
46
+ for seq in sequences:
47
+ variants = _make_seq_variants(seq)
48
+ all_variants.update(variants)
49
+ variants_by_seq[seq] = variants
50
+
51
+ return {
52
+ seq: set(variant for variant in variants if all_variants[variant] <= 1)
53
+ for (seq, variants) in variants_by_seq.items()
54
+ }
55
+
56
+
57
+ def _make_seq_variants(seq):
58
+ return set(
59
+ seq[left:-right] + (seq[-1],)
60
+ for left in range(len(seq))
61
+ for right in range(1, len(seq[left:]) + 1)
62
+ )
@@ -0,0 +1,28 @@
1
+ import functools
2
+ import json
3
+ import re
4
+ from os import path
5
+ from typing import Any, Dict
6
+
7
+ from annet.annlib.netdev.db import find_true_sequences, get_db
8
+
9
+
10
+ @functools.lru_cache(None)
11
+ def parse_hw_model(hw_model):
12
+ prepared = _prepare_db()
13
+ (tree, all_sequences) = get_db(prepared)
14
+ true_sequences = find_true_sequences(hw_model, tree)
15
+ return (
16
+ sorted(true_sequences),
17
+ all_sequences.difference(true_sequences),
18
+ )
19
+
20
+
21
+ def _prepare_db() -> Dict[str, Any]:
22
+ try:
23
+ from library.python import resource
24
+ raw = json.loads(resource.resfs_read("contrib/python/annet/annet/annlib/netdev/devdb/data/devdb.json").decode("utf-8"))
25
+ except ImportError:
26
+ with open(path.join(path.dirname(__file__), "data", "devdb.json"), "r") as f:
27
+ raw = json.load(f)
28
+ return {tuple(seq.split(".")): re.compile(regexp) for (seq, regexp) in raw.items()}