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
@@ -0,0 +1,137 @@
1
+ {
2
+ "Cisco": "^[Cc]isco",
3
+ "Cisco.CGS": " CGS",
4
+ "Cisco.IE": " IE",
5
+ "Cisco.AIR": "AIR",
6
+ "Cisco.ASR": " ASR",
7
+ "Cisco.ASR.ASR9000": " 9\\d{3}",
8
+ "Cisco.XRV": "XRv",
9
+ "Cisco.Catalyst": "Catalyst",
10
+ "Cisco.Catalyst.C2900": " 29\\d\\d",
11
+ "Cisco.Catalyst.C2900.C2950": " 2950",
12
+ "Cisco.Catalyst.C2900.C2960": " 2960",
13
+ "Cisco.Catalyst.C2900.C2960.C2960Plus": " 2960-Plus",
14
+ "Cisco.Catalyst.C2900.C2960.C2960S": " 2960S",
15
+ "Cisco.Catalyst.C2900.C2960.C2960X": " 2960X",
16
+ "Cisco.Catalyst.C3500": " 35\\d\\d",
17
+ "Cisco.Catalyst.C3600": " 36\\d\\d",
18
+ "Cisco.Catalyst.C3700": " 37\\d\\d",
19
+ "Cisco.Catalyst.C4900": " 49\\d\\d",
20
+ "Cisco.Catalyst.C6500": " 65\\d\\d",
21
+ "Cisco.Nexus": " [Nn]exus",
22
+ "Cisco.Nexus.N3x": " 3\\d\\d\\d",
23
+ "Cisco.Nexus.N3x.N3100": " 31\\d\\d",
24
+ "Cisco.Nexus.N3x.N3432": " 3432",
25
+ "Cisco.Nexus.N3x.N3500": " 35\\d\\d",
26
+ "Cisco.Nexus.N5x": " 5\\d\\d\\d",
27
+ "Cisco.Nexus.N5x.N5000": " 50\\d\\d",
28
+ "Cisco.Nexus.N5x.N5500": " 55\\d\\d",
29
+ "Cisco.Nexus.N6x": " 6\\d\\d\\d",
30
+ "Cisco.Nexus.N6x.N6000": " 60\\d\\d",
31
+ "Cisco.Nexus.N7x": " 7\\d{3}",
32
+ "Cisco.Nexus.N7x.N7000": " 70\\d\\d",
33
+ "Cisco.Nexus.N7x.N7700": " 77\\d\\d",
34
+ "Cisco.Nexus.N9x": " (N9K-[A-Z])?9\\d\\d\\d",
35
+ "Cisco.Nexus.N9x.N9316": " 9316",
36
+ "Cisco.Nexus.N9x.N9500": " 95\\d\\d",
37
+ "Cisco.Nexus.N9x.N9364": " N9K-C9364",
38
+
39
+ "Huawei": "^[Hh]uawei",
40
+ "Huawei.CE": " CE\\d+",
41
+ "Huawei.CE.CE12800": " CE128\\d{2}",
42
+ "Huawei.CE.CE5800": " CE58\\d\\d",
43
+ "Huawei.CE.CE6800": " CE68\\d\\d",
44
+ "Huawei.CE.CE6800.CE6850": " CE6850",
45
+ "Huawei.CE.CE6800.CE6860": "CE6860",
46
+ "Huawei.CE.CE6800.CE6865": " CE6865",
47
+ "Huawei.CE.CE6800.CE6870": " CE6870",
48
+ "Huawei.CE.CE6800.CE6865E": " CE6865E",
49
+ "Huawei.CE.CE6800.CE6865FM": " CE6865FM",
50
+ "Huawei.CE.CE7800": " CE78\\d\\d",
51
+ "Huawei.CE.CE8800": " CE88\\d\\d",
52
+ "Huawei.CE.CE8800.CE8850": " CE8850",
53
+ "Huawei.CE.CE8800.CE8851": " CE8851",
54
+ "Huawei.CE.CE9800": " CE98\\d\\d",
55
+ "Huawei.Quidway": " S\\d+",
56
+ "Huawei.Quidway.S2x": " S2\\d{3}",
57
+ "Huawei.Quidway.S2x.S2300": " S23\\d\\d",
58
+ "Huawei.Quidway.S2x.S2700": " S27\\d\\d",
59
+ "Huawei.Quidway.S5300": " S53\\d\\d",
60
+ "Huawei.Quidway.S5700": " S57\\d\\d",
61
+ "Huawei.Quidway.S6700": " S67\\d\\d",
62
+ "Huawei.NE": " NE\\d+",
63
+ "Huawei.NE.NE40E": " NE40E",
64
+ "Huawei.NE.NE8000": " NE8000",
65
+ "Huawei.EI": "-EI(-|$)",
66
+ "Huawei.HI": "-HI(-|$)",
67
+ "Huawei.SI": "-SI(-|$)",
68
+
69
+ "Juniper": "^[Jj]uniper",
70
+ "Juniper.MX": " MX",
71
+ "Juniper.MX.MX2000": " MX20\\d\\d",
72
+ "Juniper.MX.MX2000.MX2010": " MX2010",
73
+ "Juniper.MX.MX2000.MX2020": " MX2020",
74
+ "Juniper.MX.MX240": " MX240",
75
+ "Juniper.MX.MX480": " MX480",
76
+ "Juniper.MX.MX80": " MX80",
77
+ "Juniper.MX.MX960": " MX960",
78
+ "Juniper.QFX": " QFX",
79
+ "Juniper.QFX.QFX5200": " QFX5200",
80
+ "Juniper.VMX": " VMX",
81
+ "Juniper.VRR": " VRR",
82
+ "Juniper.PTX": " PTX",
83
+
84
+ "Arista": "^[Aa]rista",
85
+ "Arista.A7368": " 7368",
86
+ "Arista.A7368.X4": "X4",
87
+ "Arista.A7280": " DCS-7280",
88
+ "Arista.A7260": " DCS-7260",
89
+ "Arista.A7260.CX3": "CX3",
90
+ "Arista.A7050": " 7050",
91
+ "Arista.A7050.SX3": "SX3",
92
+ "Arista.A7060": " DCS-7060",
93
+ "Arista.A7060.DX4": "DX4",
94
+
95
+ "Nokia": "^[Nn]okia",
96
+ "Nokia.NS7750": " 7750",
97
+ "Nokia.SR_1s": "SR-1s",
98
+
99
+ "PC": "^(PC|pc|[Mm]ellanox SN|[Ee]dge-?[Cc]ore|[Mm]oxa)",
100
+
101
+ "PC.Whitebox": "([Mm]ellanox SN|[Ee]dge-?[Cc]ore)",
102
+ "PC.Whitebox.Mellanox": "[Mm]ellanox",
103
+ "PC.Whitebox.Mellanox.SN": " SN",
104
+ "PC.Whitebox.Mellanox.SN.SN2100": " SN2100",
105
+ "PC.Whitebox.Mellanox.SN.SN2410": " SN2410",
106
+ "PC.Whitebox.Mellanox.SN.SN3700": " SN3700",
107
+ "PC.Whitebox.Mellanox.SN.SN4700": " SN4700",
108
+ "PC.Whitebox.Edgecore": "[Ee]dge-?[Cc]ore",
109
+ "PC.Whitebox.Edgecore.AS": "AS",
110
+ "PC.Whitebox.Edgecore.AS9736": "AS9736",
111
+ "PC.Moxa": "[Mm]oxa",
112
+ "PC.Moxa.NPort": " [Nn][Pp]ort",
113
+ "PC.Moxa.NPort.6610": "6610",
114
+
115
+ "RouterOS": "^([Rr]outer[Oo][Ss]|[Mm]ikro[Tt]ik)",
116
+
117
+ "Aruba": "^Aruba",
118
+ "Aruba.AP": " I?AP-",
119
+ "Aruba.AP.AP200": " AP-2\\d\\d",
120
+ "Aruba.AP.AP200.AP224": " AP-224",
121
+ "Aruba.AP.AP200.AP225": " AP-225",
122
+ "Aruba.AP.AP300": " AP-3\\d\\d",
123
+ "Aruba.AP.AP300.AP367": " AP-367",
124
+ "Aruba.AP.AP300.AP377": " AP-377",
125
+ "Aruba.AP.AP500": " AP-5\\d\\d",
126
+ "Aruba.AP.AP500.AP514": " AP-514",
127
+ "Aruba.AP.AP500.AP515": " AP-515",
128
+ "Aruba.AP.IAP": " IAP-",
129
+ "Aruba.AP.IAP.IAP300": " IAP-3\\d\\d",
130
+ "Aruba.AP.IAP.IAP300.IAP304": " IAP-304",
131
+ "Aruba.AP.IAP.IAP300.IAP324": " IAP-324",
132
+ "Aruba.AP.IAP.IAP300.IAP325": " IAP-325",
133
+
134
+ "Ribbon": "^Ribbon",
135
+ "Ribbon.OPT": " OPT",
136
+ "Ribbon.OPT.OPT9608": "9608D?"
137
+ }
File without changes
@@ -0,0 +1,121 @@
1
+ import inspect
2
+ import sys
3
+ import traceback
4
+
5
+
6
+ class DumpResult(str):
7
+ """
8
+ строка, у которой repr = value
9
+ """
10
+ def __new__(cls, value):
11
+ return super().__new__(cls, value)
12
+
13
+ __repr__ = str.__str__
14
+
15
+
16
+ class _EnumAllAttrs:
17
+ pass
18
+
19
+
20
+ class DumpableView:
21
+ def _enum_attrs(self):
22
+ for attr in dir(self):
23
+ if not attr.startswith("_") and attr != "dump":
24
+ yield attr
25
+
26
+ def _getter_for_dump(self, attr):
27
+ dumper_name = "_dump_" + attr
28
+ if hasattr(self, dumper_name) and callable(getattr(self, dumper_name)):
29
+ return getattr(self, dumper_name)
30
+
31
+ @staticmethod
32
+ def strip_dumper(attr, lines=None, chars=None, chars_in_line=None):
33
+ def bound_method(self):
34
+ lines_list = []
35
+ value = getattr(self, attr)
36
+ assert isinstance(value, str)
37
+ total_lines = 0
38
+ total_chars = 0
39
+ add_tail = True
40
+ for line in value.split("\n"):
41
+ if lines and total_lines >= lines:
42
+ break
43
+
44
+ if chars and total_chars + len(line) >= chars:
45
+ lines_list.append(line[chars - total_chars:])
46
+ break
47
+
48
+ if chars_in_line and len(line) > chars_in_line:
49
+ lines_list.append(line[chars - total_chars:] + " ...")
50
+ else:
51
+ lines_list.append(line)
52
+ total_lines += 1
53
+ total_chars += len(line) + 1
54
+ else:
55
+ add_tail = False
56
+
57
+ ret = repr("\n".join(lines_list))
58
+ if add_tail:
59
+ ret += " ..."
60
+ return DumpResult(ret)
61
+ return bound_method
62
+
63
+ def __dump_value(self, prefix, value, seen):
64
+ ret = []
65
+ op = "="
66
+ if isinstance(value, DumpableView):
67
+ # защита от рекурсии - последующие ссылки на объекты, а не новые дампы
68
+ if id(value) in seen:
69
+ value = DumpResult(seen[id(value)])
70
+ op = "->"
71
+ else:
72
+ seen[id(value)] = prefix
73
+
74
+ if isinstance(value, DumpableView):
75
+ ret += value.dump(prefix, seen=seen) # pylint: disable=no-member
76
+ else:
77
+ fmt = repr(value)
78
+ vtype = type(value)
79
+ if not isinstance(value, DumpResult) and (vtype.__module__ != "builtins") and vtype.__name__ not in fmt:
80
+ fmt += " # %s" % (vtype)
81
+ ret += ["%s %s %s" % (prefix, op, fmt)]
82
+ return ret
83
+
84
+ def dump(self, prefix="", value=_EnumAllAttrs, seen=None):
85
+ """
86
+ В собственных DumpableView-классах нужно определить dump примерно так:
87
+ def dump(self, prefix, **kwargs):
88
+ ret = super().dump(prefix, **kwargs) + [ your_own_lines ]
89
+ """
90
+ if seen is None:
91
+ seen = {id(self): prefix}
92
+
93
+ ret = []
94
+ if value is not _EnumAllAttrs:
95
+ ret.extend(self.__dump_value(prefix, value, seen))
96
+ else:
97
+ for attr in self._enum_attrs():
98
+ getter = self._getter_for_dump(attr)
99
+ try:
100
+ if getter:
101
+ value = getter()
102
+ else:
103
+ value = getattr(self, attr)
104
+ if inspect.isroutine(value):
105
+ continue
106
+ except Exception as exc:
107
+ exc_tb = sys.exc_info()[2]
108
+ frame_index = -1
109
+ tb_list = traceback.extract_tb(exc_tb)
110
+ value = DumpResult("%r at %s:%s" % (exc, tb_list[frame_index][0], tb_list[frame_index][1]))
111
+
112
+ ret.extend(self.__dump_value("%s.%s" % (prefix, attr), value, seen))
113
+ return ret
114
+
115
+ # Filter out cached lru_methods from state
116
+ def __getstate__(self):
117
+ state = self.__dict__.copy()
118
+ for key in list(state.keys()): # For thread-safe: dictionary changed size during iteration
119
+ if inspect.ismethod(state[key]):
120
+ del state[key]
121
+ return state
@@ -0,0 +1,112 @@
1
+ from typing import Optional
2
+
3
+ from annet.annlib.netdev.devdb import parse_hw_model
4
+
5
+ from .dump import DumpableView
6
+
7
+
8
+ class HardwareLeaf(DumpableView):
9
+ def __new__(cls, *_, **__):
10
+ obj = super(HardwareLeaf, cls).__new__(cls)
11
+ obj.__path = ()
12
+ obj.__true_sequences = set()
13
+ obj.__false_sequences = set()
14
+ return obj
15
+
16
+ def __init__(self, path, true_sequences, false_sequences):
17
+ self.__path = path
18
+ self.__true_sequences = true_sequences
19
+ self.__false_sequences = false_sequences
20
+
21
+ def __bool__(self):
22
+ if len(self.__path) == 0 or self.__path in self.__true_sequences:
23
+ return True
24
+ elif self.__path in self.__false_sequences:
25
+ return False
26
+ raise AttributeError("HW: " + ".".join(self.__path))
27
+
28
+ def __getattr__(self, name):
29
+ path = self.__path + (name,)
30
+ if path in self.__true_sequences or path in self.__false_sequences:
31
+ return HardwareLeaf(path, self.__true_sequences, self.__false_sequences)
32
+ try:
33
+ return self.__dict__[name]
34
+ except KeyError:
35
+ raise AttributeError("HW: " + ".".join(path))
36
+
37
+ def __str__(self):
38
+ return str(" | ".join(".".join(x) for x in self.__true_sequences))
39
+
40
+ __repr__ = __str__
41
+
42
+ def dump(self, prefix, **kwargs): # pylint: disable=arguments-differ
43
+ ret = super().dump(prefix, **kwargs)
44
+ seen = set()
45
+ for seq in sorted(self.__true_sequences, key=len, reverse=True):
46
+ if any(name in seen for name in seq):
47
+ continue
48
+ seen.update(seq)
49
+ ret.append("%s.%s = True" % (prefix, ".".join(seq)))
50
+ return ret
51
+
52
+
53
+ class HardwareView(HardwareLeaf):
54
+ def __init__(self, hw_model, sw_version):
55
+ (true_sequences, false_sequences) = parse_hw_model(hw_model or "")
56
+ super().__init__((), true_sequences, false_sequences)
57
+ self.model = hw_model or ""
58
+ self._soft = sw_version or ""
59
+
60
+ @property
61
+ def vendor(self) -> Optional[str]:
62
+ return hw_to_vendor(self)
63
+
64
+ def __hash__(self):
65
+ return hash(self.model)
66
+
67
+ def __eq__(self, other):
68
+ return self.model == other.model
69
+
70
+
71
+ def hw_to_vendor(hw: HardwareView) -> Optional[str]:
72
+ if hw.Nexus:
73
+ return "nexus"
74
+ elif hw.Cisco:
75
+ return "cisco"
76
+ elif hw.Huawei:
77
+ return "huawei"
78
+ elif hw.Juniper:
79
+ return "juniper"
80
+ elif hw.Arista:
81
+ return "arista"
82
+ elif hw.PC:
83
+ return "pc"
84
+ elif hw.Nokia:
85
+ return "nokia"
86
+ elif hw.RouterOS:
87
+ return "routeros"
88
+ elif hw.Aruba:
89
+ return "aruba"
90
+ elif hw.Ribbon:
91
+ return "ribbon"
92
+ return None
93
+
94
+
95
+ def vendor_to_hw(vendor):
96
+ hw = HardwareView(
97
+ {
98
+ "cisco": "Cisco",
99
+ "catalyst": "Cisco Catalyst",
100
+ "nexus": "Cisco Nexus",
101
+ "huawei": "Huawei",
102
+ "juniper": "Juniper",
103
+ "arista": "Arista",
104
+ "pc": "PC",
105
+ "nokia": "Nokia",
106
+ "aruba": "Aruba",
107
+ "routeros": "RouterOS",
108
+ "ribbon": "Ribbon",
109
+ }.get(vendor.lower(), vendor),
110
+ None,
111
+ )
112
+ return hw
annet/annlib/output.py ADDED
@@ -0,0 +1,246 @@
1
+ import abc
2
+ import dataclasses
3
+ import enum
4
+ import io
5
+ import json
6
+ import os
7
+ import sys
8
+ import types
9
+ from collections import OrderedDict as odict
10
+ from contextlib import contextmanager
11
+ from typing import Any, List, Pattern
12
+
13
+ import colorama
14
+ import pygments
15
+ import pygments.formatters
16
+ import pygments.lexers.data
17
+ import yaml
18
+
19
+ LABEL_NEW_PREFIX = "new: "
20
+
21
+
22
+ # =====
23
+ class OutputWriter:
24
+ def __init__(self, data):
25
+ self.data = data
26
+
27
+ def write(self, file):
28
+ if isinstance(self.data, types.GeneratorType):
29
+ for n in self.data:
30
+ file.write(n)
31
+ return
32
+ file.write(self.data)
33
+
34
+ if self.data and self.data[-1:] != "\n":
35
+ file.write("\n")
36
+
37
+
38
+ class YamlWriter(OutputWriter):
39
+ def write(self, file):
40
+ print_as_yaml(self.data, file)
41
+
42
+
43
+ class JsonWriter(OutputWriter):
44
+ def write(self, file):
45
+ print_as_json(self.data, file)
46
+
47
+
48
+ def dir_or_file_output(dest, items_count, create_dir=True, suggest_dir=False):
49
+ if dest is None:
50
+ dest = ""
51
+
52
+ dir_mode = dest.endswith(os.sep) or suggest_dir or dest not in ("-", "") and items_count > 1
53
+
54
+ if os.path.exists(dest):
55
+ is_dir = os.path.isdir(dest)
56
+ if dir_mode and not is_dir:
57
+ raise EnvironmentError(
58
+ f"Output set to dir {dest}, but file with the same name is on the way"
59
+ )
60
+ dir_mode = dir_mode or is_dir
61
+ elif create_dir and dest not in ("-", ""):
62
+ pdir = dest if dir_mode else os.path.dirname(dest)
63
+ if pdir:
64
+ os.makedirs(pdir, exist_ok=True)
65
+
66
+ return dir_mode
67
+
68
+
69
+ def print_label(label, back_color=colorama.Back.GREEN, file=None):
70
+ if file is None:
71
+ file = sys.stdout
72
+ tty = bool(os.isatty(file.fileno()))
73
+ print("%s%s# %s %s %s%s%s" % (
74
+ colorama.Style.BRIGHT * tty, back_color * tty,
75
+ "-" * 20, label, "-" * 20,
76
+ colorama.Back.RESET * tty, colorama.Style.RESET_ALL * tty,
77
+ ), file=file)
78
+
79
+
80
+ def print_err_label(label):
81
+ print_label(label, colorama.Back.RED)
82
+
83
+
84
+ def print_as_yaml(data, fh=sys.stdout):
85
+ class UnsortableTuple(tuple):
86
+ def __lt__(self, _):
87
+ raise TypeError
88
+
89
+ class Dumper(yaml.Dumper):
90
+ def ignore_aliases(self, data):
91
+ return True
92
+
93
+ def represent_ordered_dict(self, data):
94
+ return self.represent_dict([UnsortableTuple(item) for item in data.items()])
95
+
96
+ Dumper.add_representer(odict, Dumper.represent_ordered_dict)
97
+
98
+ def yaml_dump(data, **kwargs):
99
+ return yaml.dump(data, allow_unicode=True, indent=4, Dumper=Dumper, **kwargs)
100
+
101
+ if fh.isatty():
102
+ pygments.highlight(
103
+ code=yaml_dump(data, width=os.get_terminal_size().columns),
104
+ lexer=pygments.lexers.data.YamlLexer(),
105
+ formatter=pygments.formatters.TerminalFormatter(bg="dark"),
106
+ outfile=fh,
107
+ )
108
+ else:
109
+ fh.write(yaml_dump(data))
110
+
111
+
112
+ class Dumpable(abc.ABC):
113
+ def dump(self) -> Any:
114
+ pass
115
+
116
+
117
+ def print_as_json(data, fh=sys.stdout):
118
+ """
119
+ В выводе dict ключи отсортированы по имени, кроме ключей в OrderedDict,
120
+ которые сохраняют свой оригинальный порядок
121
+ """
122
+
123
+ class ODictKey(str):
124
+ """
125
+ Строка с дополнительным параметром :index, по которому сортируется
126
+ """
127
+
128
+ @staticmethod
129
+ def __new__(cls, str_value, index=0):
130
+ self = str.__new__(cls, str_value)
131
+ self.ind = index
132
+ return self
133
+
134
+ def __lt__(self, other):
135
+ assert type(self) is type(other)
136
+ return (self.ind, self) < (other.ind, other) # pylint: disable=no-member
137
+
138
+ class UnsortableOdict(odict):
139
+ """
140
+ Разновидность OrderedDict, ключи которого сохраняют порядок
141
+ при попытке строковой сортировки (содержат доп. индекс)
142
+ """
143
+
144
+ def __init__(self, orig):
145
+ super().__init__((ODictKey(key, index), value)
146
+ for (index, (key, value)) in enumerate(orig.items()))
147
+
148
+ @staticmethod
149
+ def convert_odicts(data):
150
+ self = UnsortableOdict.convert_odicts
151
+ if isinstance(data, dict):
152
+ data = type(data)((key, self(value)) for (key, value) in data.items())
153
+ elif isinstance(data, (tuple, list)):
154
+ data = type(data)(self(value) for value in data)
155
+ if isinstance(data, odict):
156
+ data = UnsortableOdict(data)
157
+ return data
158
+
159
+ class JsonEncoder(json.JSONEncoder):
160
+ def default(self, o):
161
+ # https://github.com/PyCQA/pylint/issues/3537
162
+ if isinstance(o, Pattern): # pylint: disable=isinstance-second-argument-not-valid-type
163
+ return "(regex obj)/" + o.pattern + "/ (not a string)"
164
+ elif isinstance(o, Dumpable):
165
+ return o.dump()
166
+ elif dataclasses.is_dataclass(o):
167
+ return dataclasses.asdict(o)
168
+ elif isinstance(o, enum.Enum):
169
+ return o.value
170
+
171
+ raise TypeError("can't serialize %r to json" % o)
172
+
173
+ def encode(self, o):
174
+ try:
175
+ return super().encode(o)
176
+ except TypeError:
177
+ raise TypeError("can't serialize %r to json" % o)
178
+
179
+ dump = json.dumps(UnsortableOdict.convert_odicts(data), cls=JsonEncoder,
180
+ ensure_ascii=False, indent=4, sort_keys=True) + "\n"
181
+ if fh.isatty():
182
+ pygments.highlight(
183
+ code=dump,
184
+ lexer=pygments.lexers.data.JsonLexer(),
185
+ formatter=pygments.formatters.TerminalFormatter(bg="dark"),
186
+ outfile=fh,
187
+ )
188
+ else:
189
+ fh.write(dump)
190
+
191
+
192
+ class TextArgs:
193
+ __slots__ = ("text", "color", "offset")
194
+
195
+ def __init__(self, text, color=None, offset=None):
196
+ self.text = text
197
+ self.color = color
198
+ self.offset = offset # смещение от начала линии
199
+
200
+ def __repr__(self):
201
+ return "%s(%r, %s, %s)" % (self.__class__.__name__, self.text, self.color, self.offset)
202
+
203
+
204
+ # =====
205
+ @contextmanager
206
+ def capture_output(new_stdout: io.BufferedRandom = None, new_stderr: io.BufferedRandom = None):
207
+ streams = (
208
+ (sys.stdout, new_stdout),
209
+ (sys.stderr, new_stderr),
210
+ )
211
+ backup = []
212
+ for (std_stream, new_stream) in streams:
213
+ if new_stream is not None:
214
+ new_stream.seek(0)
215
+ new_stream.truncate(0)
216
+ std_fd = std_stream.fileno()
217
+ std_stream.flush()
218
+ backup.append((std_fd, os.fdopen(os.dup(std_fd), "w"), new_stream))
219
+ os.dup2(new_stream.fileno(), std_fd)
220
+ try:
221
+ yield
222
+ finally:
223
+ for (std_fd, backup_stream, stream) in backup:
224
+ backup_stream.flush()
225
+ os.dup2(backup_stream.fileno(), std_fd)
226
+ backup_stream.close()
227
+ stream.seek(0)
228
+
229
+
230
+ def format_file_diff(diff_lines: List[str]) -> List[str]:
231
+ colors = (
232
+ ("+++", colorama.Fore.CYAN),
233
+ ("---", colorama.Fore.CYAN),
234
+ ("@@", colorama.Fore.CYAN),
235
+ ("+", colorama.Fore.GREEN),
236
+ ("-", colorama.Fore.RED),
237
+ )
238
+ ret = []
239
+ for line in diff_lines:
240
+ for (check, color) in colors:
241
+ if line.startswith(check):
242
+ ret.append(color + line + colorama.Fore.RESET)
243
+ break
244
+ else:
245
+ ret.append(line)
246
+ return ret