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

Files changed (113) hide show
  1. annet/__init__.py +61 -0
  2. annet/annet.py +25 -0
  3. annet/annlib/__init__.py +7 -0
  4. annet/annlib/command.py +49 -0
  5. annet/annlib/diff.py +158 -0
  6. annet/annlib/errors.py +8 -0
  7. annet/annlib/filter_acl.py +196 -0
  8. annet/annlib/jsontools.py +89 -0
  9. annet/annlib/lib.py +495 -0
  10. annet/annlib/netdev/__init__.py +0 -0
  11. annet/annlib/netdev/db.py +62 -0
  12. annet/annlib/netdev/devdb/__init__.py +28 -0
  13. annet/annlib/netdev/devdb/data/devdb.json +137 -0
  14. annet/annlib/netdev/views/__init__.py +0 -0
  15. annet/annlib/netdev/views/dump.py +121 -0
  16. annet/annlib/netdev/views/hardware.py +112 -0
  17. annet/annlib/output.py +246 -0
  18. annet/annlib/patching.py +533 -0
  19. annet/annlib/rbparser/__init__.py +0 -0
  20. annet/annlib/rbparser/acl.py +120 -0
  21. annet/annlib/rbparser/deploying.py +55 -0
  22. annet/annlib/rbparser/ordering.py +52 -0
  23. annet/annlib/rbparser/platform.py +51 -0
  24. annet/annlib/rbparser/syntax.py +115 -0
  25. annet/annlib/rulebook/__init__.py +0 -0
  26. annet/annlib/rulebook/common.py +350 -0
  27. annet/annlib/tabparser.py +648 -0
  28. annet/annlib/types.py +35 -0
  29. annet/api/__init__.py +807 -0
  30. annet/argparse.py +415 -0
  31. annet/cli.py +192 -0
  32. annet/cli_args.py +493 -0
  33. annet/configs/context.yml +18 -0
  34. annet/configs/logging.yaml +39 -0
  35. annet/connectors.py +64 -0
  36. annet/deploy.py +441 -0
  37. annet/diff.py +85 -0
  38. annet/executor.py +551 -0
  39. annet/filtering.py +40 -0
  40. annet/gen.py +828 -0
  41. annet/generators/__init__.py +987 -0
  42. annet/generators/common/__init__.py +0 -0
  43. annet/generators/common/initial.py +33 -0
  44. annet/hardware.py +45 -0
  45. annet/implicit.py +139 -0
  46. annet/lib.py +128 -0
  47. annet/output.py +170 -0
  48. annet/parallel.py +448 -0
  49. annet/patching.py +25 -0
  50. annet/reference.py +148 -0
  51. annet/rulebook/__init__.py +114 -0
  52. annet/rulebook/arista/__init__.py +0 -0
  53. annet/rulebook/arista/iface.py +16 -0
  54. annet/rulebook/aruba/__init__.py +16 -0
  55. annet/rulebook/aruba/ap_env.py +146 -0
  56. annet/rulebook/aruba/misc.py +8 -0
  57. annet/rulebook/cisco/__init__.py +0 -0
  58. annet/rulebook/cisco/iface.py +68 -0
  59. annet/rulebook/cisco/misc.py +57 -0
  60. annet/rulebook/cisco/vlandb.py +90 -0
  61. annet/rulebook/common.py +19 -0
  62. annet/rulebook/deploying.py +87 -0
  63. annet/rulebook/huawei/__init__.py +0 -0
  64. annet/rulebook/huawei/aaa.py +75 -0
  65. annet/rulebook/huawei/bgp.py +97 -0
  66. annet/rulebook/huawei/iface.py +33 -0
  67. annet/rulebook/huawei/misc.py +337 -0
  68. annet/rulebook/huawei/vlandb.py +115 -0
  69. annet/rulebook/juniper/__init__.py +107 -0
  70. annet/rulebook/nexus/__init__.py +0 -0
  71. annet/rulebook/nexus/iface.py +92 -0
  72. annet/rulebook/patching.py +143 -0
  73. annet/rulebook/ribbon/__init__.py +12 -0
  74. annet/rulebook/texts/arista.deploy +20 -0
  75. annet/rulebook/texts/arista.order +125 -0
  76. annet/rulebook/texts/arista.rul +59 -0
  77. annet/rulebook/texts/aruba.deploy +20 -0
  78. annet/rulebook/texts/aruba.order +83 -0
  79. annet/rulebook/texts/aruba.rul +87 -0
  80. annet/rulebook/texts/cisco.deploy +27 -0
  81. annet/rulebook/texts/cisco.order +82 -0
  82. annet/rulebook/texts/cisco.rul +105 -0
  83. annet/rulebook/texts/huawei.deploy +188 -0
  84. annet/rulebook/texts/huawei.order +388 -0
  85. annet/rulebook/texts/huawei.rul +471 -0
  86. annet/rulebook/texts/juniper.rul +120 -0
  87. annet/rulebook/texts/nexus.deploy +24 -0
  88. annet/rulebook/texts/nexus.order +85 -0
  89. annet/rulebook/texts/nexus.rul +83 -0
  90. annet/rulebook/texts/nokia.rul +31 -0
  91. annet/rulebook/texts/pc.order +5 -0
  92. annet/rulebook/texts/pc.rul +9 -0
  93. annet/rulebook/texts/ribbon.deploy +22 -0
  94. annet/rulebook/texts/ribbon.rul +77 -0
  95. annet/rulebook/texts/routeros.order +38 -0
  96. annet/rulebook/texts/routeros.rul +45 -0
  97. annet/storage.py +121 -0
  98. annet/tabparser.py +36 -0
  99. annet/text_term_format.py +95 -0
  100. annet/tracing.py +170 -0
  101. annet/types.py +223 -0
  102. annet-0.1.dist-info/AUTHORS +21 -0
  103. annet-0.1.dist-info/LICENSE +21 -0
  104. annet-0.1.dist-info/METADATA +24 -0
  105. annet-0.1.dist-info/RECORD +113 -0
  106. annet-0.1.dist-info/WHEEL +5 -0
  107. annet-0.1.dist-info/entry_points.txt +6 -0
  108. annet-0.1.dist-info/top_level.txt +3 -0
  109. annet_generators/__init__.py +0 -0
  110. annet_generators/example/__init__.py +12 -0
  111. annet_generators/example/lldp.py +52 -0
  112. annet_nbexport/__init__.py +220 -0
  113. annet_nbexport/main.py +46 -0
annet/cli_args.py ADDED
@@ -0,0 +1,493 @@
1
+ # pylint: disable=too-many-ancestors
2
+
3
+ import abc
4
+ import argparse
5
+ import enum
6
+ import os
7
+
8
+ from valkit.common import valid_string_list
9
+
10
+ from annet.argparse import Arg, ArgGroup, DefaultFromEnv
11
+ from annet.hardware import hardware_connector
12
+ from annet.storage import Query, storage_connector
13
+
14
+
15
+ # ====
16
+ def valid_vendor(vendor):
17
+ hw_provider = hardware_connector.get()
18
+ hw = hw_provider.vendor_to_hw(vendor)
19
+ if hw:
20
+ return hw.vendor
21
+ return ""
22
+
23
+
24
+ def convert_to_none(arg):
25
+ return None if arg == "-" else arg
26
+
27
+
28
+ def valid_config_source(value):
29
+ if value not in ["cfglister", "running", "empty", "-"] and not os.path.exists(value):
30
+ raise ValueError("No such file or directory %r" % value)
31
+ return value
32
+
33
+
34
+ def valid_range(value: str):
35
+ if value.isdigit():
36
+ return slice(0, int(value))
37
+ elif ":" in value:
38
+ start_str, stop_str = value.split(":", 1)
39
+ if not stop_str:
40
+ stop = None
41
+ else:
42
+ stop = int(stop_str)
43
+ return slice(int(start_str), stop)
44
+
45
+ raise ValueError("Invalid range: %s" % value)
46
+
47
+
48
+ def opt_query_factory(**kwargs):
49
+ return Arg(
50
+ "query",
51
+ help="Запрос, определяющий список устройств. Принимается fqdn, rackcode, глоб"
52
+ " или путь к файлу со списком запросов (нужно писать @path/to/file)",
53
+ **kwargs,
54
+ )
55
+
56
+
57
+ # ====
58
+ opt_query = opt_query_factory(nargs="+")
59
+
60
+ opt_query_optional = opt_query_factory(nargs="*", default=[])
61
+
62
+ opt_dest = Arg(
63
+ "--dest", type=convert_to_none,
64
+ help="Файл или каталог для вывода сгенерированных данных"
65
+ )
66
+
67
+ opt_expand_path = Arg(
68
+ "--expand-path", default=False,
69
+ help="Разворачивать пути entire-генераторов при записи их на файловую систему"
70
+ )
71
+
72
+ opt_old = Arg(
73
+ "old",
74
+ help="Файл со старым конфигом (или каталог с пачкой)"
75
+ )
76
+
77
+ opt_new = Arg(
78
+ "new",
79
+ help="Файл с новым конфигом (или каталог с пачкой)"
80
+ )
81
+
82
+ opt_hw = Arg(
83
+ "--hw", default="", type=valid_vendor,
84
+ help="Производитель устройства (например Huawei или Cisco) или полное название модели. Если его нет - пытаемся его задетектить"
85
+ )
86
+
87
+ opt_indent = Arg(
88
+ "--indent", default=" ",
89
+ help="Отступ при форматировании блоков"
90
+ )
91
+
92
+ opt_allowed_gens = Arg(
93
+ "-g", "--allowed-gens", type=valid_string_list,
94
+ help="Список классов генераторов через запятую, которые нужно запустить"
95
+ )
96
+
97
+ opt_excluded_gens = Arg(
98
+ "-G", "--excluded-gens", type=valid_string_list,
99
+ help="Список классов генераторов через запятую, запуск которых следует исключить"
100
+ )
101
+
102
+ opt_force_enabled = Arg(
103
+ "--force-enabled", type=valid_string_list,
104
+ help="Список классов генераторов через запятую, которые не нужно считать DISABLED даже при наличии тега"
105
+ )
106
+
107
+ opt_generators_context = Arg(
108
+ "--generators_context", type=str, default=None,
109
+ help=argparse.SUPPRESS
110
+ )
111
+
112
+ opt_no_acl = Arg(
113
+ "--no-acl", default=False,
114
+ help="Отключение ACL при генерации"
115
+ )
116
+
117
+ opt_no_acl_exclusive = Arg(
118
+ "--no-acl-exclusive", default=False,
119
+ help="Проверяем что ACL выполненных генераторов не пересекаются"
120
+ )
121
+
122
+ opt_acl_safe = Arg(
123
+ "--acl-safe", default=False,
124
+ help="Использовать более строгий safe acl для фильтрации результата генерации"
125
+ )
126
+
127
+ opt_show_rules = Arg(
128
+ "--show-rules", default=False,
129
+ help="Показывать правила rulebook при выводе диффа"
130
+ )
131
+
132
+ opt_tolerate_fails = Arg(
133
+ "--tolerate-fails", default=False,
134
+ help="Рапортовать об ошибках без остановки генерации"
135
+ )
136
+
137
+ # При параллельном запуске и включённом --tolerate-fails код возврата
138
+ # всегда нулевой. Это не позволяет нам легко понять, прошла ли генерация
139
+ # успешно для всех устройств. С этим флажком код будет ненулевой, если
140
+ # генерация упала хотя бы для одного устройства. А в хелпе эту переменную
141
+ # не выводим, там и так не протолкнуться от флагов.
142
+ opt_strict_exit_code = Arg(
143
+ "--strict-exit-code", default=False,
144
+ help=argparse.SUPPRESS,
145
+ )
146
+
147
+ opt_required_packages_check = Arg(
148
+ "--required-packages-check", default=False,
149
+ help="Включить проверку наличия установленных deb-пакетов для Entire-генераторов"
150
+ )
151
+
152
+ opt_profile = Arg(
153
+ "--profile", default=False,
154
+ help="Показать в stderr время, затраченное на работу генераторов и обращениям к RackTables"
155
+ )
156
+
157
+
158
+ opt_parallel = Arg(
159
+ "-P", "--parallel", type=int, default=1,
160
+ help="Количество одновременных потоков генерирования"
161
+ )
162
+
163
+ opt_max_tasks = Arg(
164
+ "--max-tasks", type=int, default=None,
165
+ help="Рестартовать воркеры каждые N устройств, для сброса кеша и ограничения потребления памяти"
166
+ "По умолчанию - не рестартовать"
167
+ )
168
+
169
+ opt_annotate = Arg(
170
+ "--annotate", default=False,
171
+ help="Добавить к сгенерированному конфигу комментарии о том откуда строчка взялась"
172
+ )
173
+
174
+ opt_config = Arg(
175
+ "--config", default="running", type=valid_config_source,
176
+ help="'cfglister', 'running', 'empty', путь к файлу конфига, "
177
+ "каталогу с файлами конфига в формате <hostname>.cfg "
178
+ "или '-' (stdin)"
179
+ )
180
+
181
+ opt_clear = Arg(
182
+ "--clear", default=False,
183
+ help="Используя acl вычищает команды относящиеся к данному генератору"
184
+ "аналогично использованию return в самом начале генератора"
185
+ )
186
+
187
+ opt_filter_acl = Arg(
188
+ "--filter-acl", default="",
189
+ help="путь к файлу с дополнительным фильтрующим acl, или '-' (stdin)"
190
+ )
191
+
192
+ opt_filter_ifaces = Arg(
193
+ "-i", "--filter-ifaces", default=[], type=valid_string_list,
194
+ help="Генерирует filter-acl по имени интерфейса. "
195
+ "Принимает регекспы, через запятую: '-i 10GE,100GE'. "
196
+ "По-умолчанию добавляет '.*' к концу каждого. "
197
+ "Для указания имени точно, следует добавлять '$': '-i ae0$'. "
198
+ "Если filter-acl передан напрямую, данная опция игнорируется."
199
+ )
200
+
201
+ opt_filter_peers = Arg(
202
+ "-fp", "--filter-peers", default=[], type=valid_string_list,
203
+ help="Генерирует filter-acl по адресу/имени группы/дескрипшену пира."
204
+ )
205
+
206
+ opt_filter_policies = Arg(
207
+ "-frp", "--filter-policies", default=[], type=valid_string_list,
208
+ help="Генерирует filter-acl по названию политик, название должно строго соответствовать, частичные имена не пройдут"
209
+ )
210
+
211
+ opt_ask_pass = Arg(
212
+ "--ask-pass", default=False,
213
+ help="Спросить пароль на подключение"
214
+ )
215
+
216
+ opt_no_ask_deploy = Arg(
217
+ "--no-ask-deploy", default=False,
218
+ help="Не подтвеждать команды перед выполнением"
219
+ )
220
+
221
+ opt_no_progress = Arg(
222
+ "--no-progress", default=False,
223
+ help="Выключить графику прогресс баров комокутора"
224
+ )
225
+
226
+ opt_log_json = Arg(
227
+ "--log-json", default=False,
228
+ help="Логгировать в формате json (default: plain text)"
229
+ )
230
+
231
+ opt_log_dest = Arg(
232
+ "--log-dest", default="deploy/",
233
+ help="Логгировать в указанный файл/директорию, или в stdout, если указать '-'"
234
+ )
235
+
236
+ opt_log_nogroup = Arg(
237
+ "--log-nogroup", default=False,
238
+ help="Не создавать в директории LOG-DEST поддиректории DATE_TIME/"
239
+ )
240
+
241
+ opt_max_slots = Arg(
242
+ "--max-slots", default=30, type=int,
243
+ help="Количество одновременно обрабатываемых asyncio устройств"
244
+ )
245
+
246
+ opt_hosts_range = Arg(
247
+ "--hosts-range", type=valid_range,
248
+ help="Обработать только указанный диапазон хостов. 10 - первые 10. 10:20 - хосты с 10-го по 20-ый"
249
+ )
250
+
251
+ opt_add_comments = Arg(
252
+ "--add-comments", default=False,
253
+ help="Добавлять комменты подтверждения для rbprocess"
254
+ )
255
+
256
+ opt_no_label = Arg(
257
+ "--no-label", default=False,
258
+ help="Убрать лейбл с именем файла из вывода"
259
+ )
260
+
261
+ opt_no_color = Arg(
262
+ "--no-color", default=False,
263
+ help="Не делать ANSI-раскраску вывода (при --dest включён)"
264
+ )
265
+
266
+ opt_no_check_diff = Arg(
267
+ "--no-check-diff", default=False,
268
+ help="не запрашивать дифф после деплоя"
269
+ )
270
+
271
+ opt_dont_commit = Arg(
272
+ "--dont-commit", default=False,
273
+ help="не добавлять команду commit во время деплоя"
274
+ )
275
+
276
+ opt_rollback = Arg(
277
+ "--rollback", default=False,
278
+ help="предложить откат после деплоя где это возможно"
279
+ )
280
+
281
+ opt_fail_on_empty_config = Arg(
282
+ "--fail-on-empty-config", default=False,
283
+ help=argparse.SUPPRESS,
284
+ )
285
+
286
+
287
+ opt_show_generators_format = Arg(
288
+ "--format", default="text", choices=["text", "json"],
289
+ help="Формат выдачи"
290
+ )
291
+
292
+
293
+ class EntireReloadFlag(enum.Enum):
294
+ no = "no"
295
+ yes = "yes"
296
+ force = "force"
297
+
298
+ def __bool__(self):
299
+ return self is not self.no
300
+
301
+ def __str__(self):
302
+ return str(self.value)
303
+
304
+ __repr__ = __str__
305
+
306
+
307
+ opt_entire_reload = Arg(
308
+ "--entire-reload",
309
+ type=EntireReloadFlag,
310
+ default=EntireReloadFlag.yes,
311
+ choices=list(EntireReloadFlag),
312
+ const=EntireReloadFlag.yes,
313
+ nargs="?",
314
+ help="Выполнить reload() при deploy'e entire генераторов. "
315
+ "no/yes/force - нет/только если файл изменился/даже если не изменился"
316
+ )
317
+
318
+ opt_show_hosts_progress = Arg(
319
+ "--show-hosts-progress", default=False,
320
+ help="Показывать проценты выполнения по хостам"
321
+ )
322
+
323
+ opt_no_collapse = Arg(
324
+ "--no-collapse", default=False,
325
+ help="Не схлопывать одинаковые diff для группы устройств (при --dest включён)"
326
+ )
327
+
328
+ opt_fails_only = Arg(
329
+ "--fails-only", default=False,
330
+ help="Показать только устройства с ошибками"
331
+ )
332
+
333
+ opt_connect_timeout = Arg(
334
+ "--connect-timeout", default=DefaultFromEnv("ANN_CONNECT_TIMEOUT", "20.0"), type=float,
335
+ help="Таймаут на подключение к устройству в секундах."
336
+ " Значение по-умолчанию можно задать в переменной окружения ANN_CONNECT_TIMEOUT"
337
+ )
338
+
339
+ opt_selected_context_name = Arg(
340
+ "context-name", type=str, help="Имя контекста в файле конфигурации"
341
+ )
342
+
343
+
344
+ # ====
345
+ class CacheOptions(ArgGroup):
346
+ no_mesh = False
347
+
348
+
349
+ class QueryOptionsBase(CacheOptions):
350
+ @property
351
+ @abc.abstractmethod
352
+ def query(self) -> "Query":
353
+ pass
354
+
355
+ @property
356
+ @abc.abstractmethod
357
+ def hosts_range(self):
358
+ pass
359
+
360
+ def __init__(self, *args, **kwargs):
361
+ super().__init__(*args, **kwargs)
362
+ if not isinstance(self.query, Query):
363
+ query_type = storage_connector.get().query()
364
+ self.query = query_type.new(self.query, hosts_range=self.hosts_range)
365
+
366
+ def validate_stdin(self, arg, val, **kwargs):
367
+ if "storage" in kwargs and arg == "config":
368
+ storage = kwargs["storage"]
369
+ if len(storage.resolve_object_ids_by_query(self.query)) > 1:
370
+ raise ValueError("stdin config can not be used with multiple devices")
371
+ super().validate_stdin(arg, val, **kwargs)
372
+
373
+
374
+ class QueryOptions(QueryOptionsBase):
375
+ query = opt_query
376
+ hosts_range = opt_hosts_range
377
+
378
+
379
+ class QueryOptionsOptional(QueryOptionsBase):
380
+ query = opt_query_optional
381
+ hosts_range = opt_hosts_range
382
+
383
+
384
+ class ParallelOptions(ArgGroup):
385
+ parallel = opt_parallel
386
+ max_tasks = opt_max_tasks
387
+
388
+
389
+ class GenSelectOptions(ArgGroup):
390
+ allowed_gens = opt_allowed_gens
391
+ excluded_gens = opt_excluded_gens
392
+ force_enabled = opt_force_enabled
393
+ generators_context = opt_generators_context
394
+ ignore_disabled = False
395
+
396
+
397
+ class GenOptions(QueryOptions, GenSelectOptions, CacheOptions, ParallelOptions):
398
+ no_acl = opt_no_acl
399
+ no_acl_exclusive = opt_no_acl_exclusive
400
+ acl_safe = opt_acl_safe
401
+ filter_acl = opt_filter_acl
402
+ filter_ifaces = opt_filter_ifaces
403
+ filter_peers = opt_filter_peers
404
+ filter_policies = opt_filter_policies
405
+ profile = opt_profile
406
+ tolerate_fails = opt_tolerate_fails
407
+ required_packages_check = opt_required_packages_check
408
+ strict_exit_code = opt_strict_exit_code
409
+ fail_on_empty_config = opt_fail_on_empty_config
410
+
411
+
412
+ class ComocutorOptions(ArgGroup):
413
+ ask_pass = opt_ask_pass
414
+ max_slots = opt_max_slots
415
+ no_progress = opt_no_progress
416
+ connect_timeout = opt_connect_timeout
417
+
418
+
419
+ class CliLoggingOptions(ArgGroup):
420
+ log_json = opt_log_json
421
+ log_dest = opt_log_dest
422
+ log_nogroup = opt_log_nogroup
423
+
424
+
425
+ class DeviceCliOptions(ComocutorOptions, CliLoggingOptions):
426
+ no_ask_deploy = opt_no_ask_deploy
427
+ dont_commit = opt_dont_commit
428
+
429
+
430
+ class FileOutOptions(ArgGroup):
431
+ dest = opt_dest
432
+ expand_path = opt_expand_path
433
+ no_label = opt_no_label
434
+ no_color = opt_no_color
435
+
436
+
437
+ class DiffOptions(GenOptions, ComocutorOptions):
438
+ clear = opt_clear
439
+ config = opt_config
440
+
441
+
442
+ class FileInputOptions(ArgGroup):
443
+ old = opt_old
444
+ new = opt_new
445
+ hw = opt_hw
446
+ fails_only = opt_fails_only
447
+
448
+
449
+ class PatchOptions(DiffOptions):
450
+ add_comments = opt_add_comments
451
+
452
+
453
+ class DeployOptions(PatchOptions, DeviceCliOptions):
454
+ no_check_diff = opt_no_check_diff
455
+ entire_reload = opt_entire_reload
456
+ rollback = opt_rollback
457
+
458
+
459
+ class ShowGenOptions(GenOptions, FileOutOptions):
460
+ indent = opt_indent
461
+ annotate = opt_annotate
462
+ show_hosts_progress = opt_show_hosts_progress
463
+
464
+
465
+ class ShowDiffOptions(DiffOptions, FileOutOptions):
466
+ indent = opt_indent
467
+ show_rules = opt_show_rules
468
+ no_collapse = opt_no_collapse
469
+
470
+
471
+ class ShowPatchOptions(PatchOptions, FileOutOptions):
472
+ indent = opt_indent
473
+ show_hosts_progress = opt_show_hosts_progress
474
+
475
+
476
+ class FileDiffOptions(FileInputOptions, FileOutOptions, ParallelOptions):
477
+ indent = opt_indent
478
+ show_rules = opt_show_rules
479
+
480
+
481
+ class FilePatchOptions(FileInputOptions, FileOutOptions, ParallelOptions):
482
+ indent = opt_indent
483
+ add_comments = opt_add_comments
484
+
485
+
486
+ class ShowGeneratorsOptions(QueryOptionsOptional, GenSelectOptions):
487
+ format = opt_show_generators_format
488
+ acl_safe = opt_acl_safe
489
+ ignore_disabled = False
490
+
491
+
492
+ class SelectContext(ArgGroup):
493
+ context_name = opt_selected_context_name
@@ -0,0 +1,18 @@
1
+ connection:
2
+ default:
3
+ login: ~
4
+ passwords: ~
5
+ enable_ssh_conf: false
6
+ no_nocauth: false
7
+ ssh_forward_agent: ~
8
+ tunnel: ~
9
+ generators:
10
+ default:
11
+ - annet_generators.example
12
+
13
+ context:
14
+ default:
15
+ connection: default
16
+ generators: default
17
+
18
+ selected_context: default
@@ -0,0 +1,39 @@
1
+ version: 1
2
+ disable_existing_loggers: false
3
+
4
+ formatters:
5
+ standard:
6
+ (): contextlog.SmartFormatter
7
+ style: "{"
8
+ datefmt: "%H:%M:%S"
9
+ format: "[{asctime}] {log_color}{levelname:>7}{reset} {processName} - {pathname}:{lineno} - {message} -- {cyan}{_extra}{reset}"
10
+ host_progress:
11
+ (): contextlog.SmartFormatter
12
+ style: "{"
13
+ datefmt: "%H:%M:%S"
14
+ format: "[{asctime}] {cyan}[{perc:>3}%]{reset} [{status_color}{bold}{status:^6}{reset}]: {status_color}{fqdn}{reset} - {message} -- {cyan}{_extra}{reset}"
15
+
16
+
17
+ handlers:
18
+ console:
19
+ level: DEBUG
20
+ class: logging.StreamHandler
21
+ formatter: standard
22
+ progress:
23
+ level: DEBUG
24
+ class: logging.StreamHandler
25
+ formatter: host_progress
26
+
27
+ root:
28
+ level: INFO
29
+ handlers:
30
+ - console
31
+
32
+ loggers:
33
+ progress:
34
+ level: INFO
35
+ propagate: False
36
+ handlers:
37
+ - progress
38
+ rtapi:
39
+ level: INFO
annet/connectors.py ADDED
@@ -0,0 +1,64 @@
1
+ import sys
2
+ from abc import ABC
3
+ from functools import cached_property
4
+ from importlib.metadata import entry_points
5
+ from typing import Generic, Optional, Type, TypeVar
6
+
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class Connector(ABC, Generic[T]):
12
+ name: str
13
+ ep_name: str
14
+ ep_group: str = "annet.connectors"
15
+ _cls: Optional[Type[T]] = None
16
+
17
+ def _get_default(self) -> Type[T]:
18
+ raise RuntimeError(f"{self.name} is not set")
19
+
20
+ @cached_property
21
+ def _entry_point(self) -> Optional[Type[T]]:
22
+ return load_entry_point(self.ep_group, self.ep_name)
23
+
24
+ def get(self, *args, **kwargs) -> T:
25
+ if self._cls is not None:
26
+ res = self._cls
27
+ else:
28
+ res = self._entry_point or self._get_default()
29
+ return res(*args, **kwargs)
30
+
31
+ def set(self, cls: Type[T]):
32
+ if self._cls is not None:
33
+ raise RuntimeError(f"Cannot reinitialize value of {self.name}")
34
+ self._cls = cls
35
+
36
+ def is_default(self) -> bool:
37
+ return self._cls is self._entry_point is None
38
+
39
+
40
+ class CachedConnector(Connector[T], ABC):
41
+ _cache: Optional[T] = None
42
+
43
+ def get(self, *args, **kwargs) -> T:
44
+ assert not (args or kwargs), "Arguments forwarding is not allowed for cached connectors"
45
+ if self._cache is None:
46
+ self._cache = super().get()
47
+ return self._cache
48
+
49
+ def set(self, cls: Type[T]):
50
+ super().set(cls)
51
+ self._cache = None
52
+
53
+
54
+ def load_entry_point(group: str, name: str):
55
+ if sys.version_info < (3, 10):
56
+ ep = [item for item in entry_points().get(group, []) if item.name == name]
57
+ else:
58
+ ep = entry_points(group=group, name=name) # pylint: disable=unexpected-keyword-arg
59
+ if not ep:
60
+ return None
61
+ if len(ep) > 1:
62
+ raise RuntimeError(f"Multiple entry points with the same {group=} and {name=}: {[item.value for item in ep]}")
63
+ for item in ep:
64
+ return item.load()