annet 0.11__py3-none-any.whl → 0.12__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.
- annet/adapters/netbox/common/models.py +10 -3
- annet/adapters/netbox/common/status_client.py +2 -1
- annet/adapters/netbox/v24/api_models.py +4 -3
- annet/adapters/netbox/v24/storage.py +12 -6
- annet/adapters/netbox/v37/api_models.py +3 -2
- annet/adapters/netbox/v37/storage.py +11 -5
- annet/api/__init__.py +167 -159
- annet/cli.py +96 -55
- annet/cli_args.py +9 -9
- annet/connectors.py +28 -15
- annet/gen.py +126 -129
- annet/generators/__init__.py +41 -627
- annet/generators/base.py +136 -0
- annet/generators/common/initial.py +1 -1
- annet/generators/entire.py +97 -0
- annet/generators/exceptions.py +10 -0
- annet/generators/jsonfragment.py +125 -0
- annet/generators/partial.py +119 -0
- annet/generators/perf.py +79 -0
- annet/generators/ref.py +15 -0
- annet/generators/result.py +127 -0
- annet/output.py +4 -9
- annet/storage.py +7 -3
- annet/types.py +0 -2
- {annet-0.11.dist-info → annet-0.12.dist-info}/METADATA +1 -1
- {annet-0.11.dist-info → annet-0.12.dist-info}/RECORD +31 -23
- {annet-0.11.dist-info → annet-0.12.dist-info}/AUTHORS +0 -0
- {annet-0.11.dist-info → annet-0.12.dist-info}/LICENSE +0 -0
- {annet-0.11.dist-info → annet-0.12.dist-info}/WHEEL +0 -0
- {annet-0.11.dist-info → annet-0.12.dist-info}/entry_points.txt +0 -0
- {annet-0.11.dist-info → annet-0.12.dist-info}/top_level.txt +0 -0
annet/generators/base.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import abc
|
|
4
|
+
import contextlib
|
|
5
|
+
import textwrap
|
|
6
|
+
from typing import Union, List
|
|
7
|
+
|
|
8
|
+
from annet import tabparser, tracing
|
|
9
|
+
from annet.tracing import tracing_connector
|
|
10
|
+
from .exceptions import InvalidValueFromGenerator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DefaultBlockIfCondition:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
ParamsList = tabparser.JuniperList
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class GenStringable(abc.ABC):
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def gen_str(self) -> str:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _filter_str(value: Union[
|
|
27
|
+
str, int, float, tabparser.JuniperList, ParamsList, GenStringable]):
|
|
28
|
+
if isinstance(value, (
|
|
29
|
+
str,
|
|
30
|
+
int,
|
|
31
|
+
float,
|
|
32
|
+
tabparser.JuniperList,
|
|
33
|
+
ParamsList,
|
|
34
|
+
)):
|
|
35
|
+
return str(value)
|
|
36
|
+
|
|
37
|
+
if hasattr(value, "gen_str") and callable(value.gen_str):
|
|
38
|
+
return value.gen_str()
|
|
39
|
+
|
|
40
|
+
raise InvalidValueFromGenerator(
|
|
41
|
+
"Invalid yield type: %s(%s)" % (type(value).__name__, value))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _split_and_strip(text):
|
|
45
|
+
if "\n" in text:
|
|
46
|
+
rows = textwrap.dedent(text).strip().split("\n")
|
|
47
|
+
else:
|
|
48
|
+
rows = [text]
|
|
49
|
+
return rows
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# =====
|
|
53
|
+
class BaseGenerator:
|
|
54
|
+
TYPE: str
|
|
55
|
+
TAGS: List[str]
|
|
56
|
+
|
|
57
|
+
def supports_device(self, device) -> bool: # pylint: disable=unused-argument
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TreeGenerator(BaseGenerator):
|
|
62
|
+
def __init__(self, indent=" "):
|
|
63
|
+
self._indents = []
|
|
64
|
+
self._rows = []
|
|
65
|
+
self._block_path = []
|
|
66
|
+
self._indent = indent
|
|
67
|
+
|
|
68
|
+
@tracing.contextmanager(min_duration="0.1")
|
|
69
|
+
@contextlib.contextmanager
|
|
70
|
+
def block(self, *tokens, indent=None):
|
|
71
|
+
span = tracing_connector.get().get_current_span()
|
|
72
|
+
if span:
|
|
73
|
+
span.set_attribute("tokens", " ".join(map(str, tokens)))
|
|
74
|
+
|
|
75
|
+
indent = self._indent if indent is None else indent
|
|
76
|
+
block = " ".join(map(_filter_str, tokens))
|
|
77
|
+
self._block_path.append(block)
|
|
78
|
+
self._append_text(block)
|
|
79
|
+
self._indents.append(indent)
|
|
80
|
+
yield
|
|
81
|
+
self._indents.pop(-1)
|
|
82
|
+
self._block_path.pop(-1)
|
|
83
|
+
|
|
84
|
+
@contextlib.contextmanager
|
|
85
|
+
def block_if(self, *tokens, condition=DefaultBlockIfCondition):
|
|
86
|
+
if condition is DefaultBlockIfCondition:
|
|
87
|
+
condition = (None not in tokens and "" not in tokens)
|
|
88
|
+
if condition:
|
|
89
|
+
with self.block(*tokens):
|
|
90
|
+
yield
|
|
91
|
+
return
|
|
92
|
+
yield
|
|
93
|
+
|
|
94
|
+
@contextlib.contextmanager
|
|
95
|
+
def multiblock(self, *blocks):
|
|
96
|
+
if blocks:
|
|
97
|
+
blk = blocks[0]
|
|
98
|
+
tokens = blk if isinstance(blk, (list, tuple)) else [blk]
|
|
99
|
+
with self.block(*tokens):
|
|
100
|
+
with self.multiblock(*blocks[1:]):
|
|
101
|
+
yield
|
|
102
|
+
return
|
|
103
|
+
yield
|
|
104
|
+
|
|
105
|
+
@contextlib.contextmanager
|
|
106
|
+
def multiblock_if(self, *blocks, condition=DefaultBlockIfCondition):
|
|
107
|
+
if condition is DefaultBlockIfCondition:
|
|
108
|
+
condition = (None not in blocks)
|
|
109
|
+
if condition:
|
|
110
|
+
if blocks:
|
|
111
|
+
blk = blocks[0]
|
|
112
|
+
tokens = blk if isinstance(blk, (list, tuple)) else [blk]
|
|
113
|
+
with self.block(*tokens):
|
|
114
|
+
with self.multiblock(*blocks[1:]):
|
|
115
|
+
yield
|
|
116
|
+
return
|
|
117
|
+
yield
|
|
118
|
+
|
|
119
|
+
# ===
|
|
120
|
+
def _append_text(self, text):
|
|
121
|
+
self._append_text_cb(text)
|
|
122
|
+
|
|
123
|
+
def _append_text_cb(self, text, row_cb=None):
|
|
124
|
+
for row in _split_and_strip(text):
|
|
125
|
+
if row_cb:
|
|
126
|
+
row = row_cb(row)
|
|
127
|
+
self._rows.append("".join(self._indents) + row)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TextGenerator(TreeGenerator):
|
|
131
|
+
def __add__(self, line):
|
|
132
|
+
self._append_text(line)
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def __iter__(self):
|
|
136
|
+
yield from self._rows
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import pkgutil
|
|
4
|
+
import re
|
|
5
|
+
import types
|
|
6
|
+
from typing import (
|
|
7
|
+
FrozenSet,
|
|
8
|
+
Iterable,
|
|
9
|
+
List,
|
|
10
|
+
Optional,
|
|
11
|
+
Set,
|
|
12
|
+
Union,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from annet.lib import (
|
|
16
|
+
flatten,
|
|
17
|
+
jinja_render,
|
|
18
|
+
mako_render,
|
|
19
|
+
)
|
|
20
|
+
from .base import BaseGenerator, _filter_str
|
|
21
|
+
from .exceptions import NotSupportedDevice
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Entire(BaseGenerator):
|
|
25
|
+
TYPE = "ENTIRE"
|
|
26
|
+
TAGS: List[str] = []
|
|
27
|
+
REQUIRED_PACKAGES: FrozenSet[str] = frozenset()
|
|
28
|
+
|
|
29
|
+
def __init__(self, storage):
|
|
30
|
+
self.storage = storage
|
|
31
|
+
# между генераторами для одного и того же path - выбирается тот что больше
|
|
32
|
+
if not hasattr(self, "prio"):
|
|
33
|
+
self.prio = 100
|
|
34
|
+
self.__device = None
|
|
35
|
+
|
|
36
|
+
def supports_device(self, device):
|
|
37
|
+
return bool(self.path(device))
|
|
38
|
+
|
|
39
|
+
def run(self, device) -> Union[None, str, Iterable[Union[str, tuple]]]:
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def reload(self, device) -> Optional[
|
|
43
|
+
str]: # pylint: disable=unused-argument
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
def get_reload_cmds(self, device) -> str:
|
|
47
|
+
ret = self.reload(device) or ""
|
|
48
|
+
path = self.path(device)
|
|
49
|
+
if path and device.hw.PC and device.hw.soft.startswith(
|
|
50
|
+
("Cumulus", "SwitchDev", "SONiC"),
|
|
51
|
+
):
|
|
52
|
+
parts = []
|
|
53
|
+
if ret:
|
|
54
|
+
parts.append(ret)
|
|
55
|
+
parts.append("/usr/bin/etckeeper commitreload %s" % path)
|
|
56
|
+
return "\n".join(parts)
|
|
57
|
+
return ret
|
|
58
|
+
|
|
59
|
+
def path(self, device) -> Optional[str]:
|
|
60
|
+
raise NotImplementedError("Required PATH for ENTIRE generator")
|
|
61
|
+
|
|
62
|
+
# pylint: disable=unused-argument
|
|
63
|
+
def is_safe(self, device) -> bool:
|
|
64
|
+
"""Output gen results when --acl-safe flag is used"""
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def read(self, path) -> str:
|
|
68
|
+
return pkgutil.get_data(__name__, path).decode()
|
|
69
|
+
|
|
70
|
+
def mako(self, text, **kwargs) -> str:
|
|
71
|
+
return mako_render(text, dedent=True, device=self.__device, **kwargs)
|
|
72
|
+
|
|
73
|
+
def jinja(self, text, **kwargs) -> str:
|
|
74
|
+
return jinja_render(text, dedent=True, device=self.__device, **kwargs)
|
|
75
|
+
|
|
76
|
+
# =====
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def get_aliases(cls) -> Set[str]:
|
|
80
|
+
return {cls.__name__, *cls.TAGS}
|
|
81
|
+
|
|
82
|
+
def __call__(self, device):
|
|
83
|
+
self.__device = device
|
|
84
|
+
parts = []
|
|
85
|
+
run_res = self.run(device)
|
|
86
|
+
if isinstance(run_res, str):
|
|
87
|
+
run_res = (run_res,)
|
|
88
|
+
if run_res is None or not isinstance(run_res, (tuple, types.GeneratorType)):
|
|
89
|
+
raise Exception("generator %s returns %s" % (
|
|
90
|
+
self.__class__.__name__, type(run_res)))
|
|
91
|
+
for text in run_res:
|
|
92
|
+
if isinstance(text, tuple):
|
|
93
|
+
text = " ".join(map(_filter_str, flatten(text)))
|
|
94
|
+
assert re.search(r"\bNone\b", text) is None, \
|
|
95
|
+
"Found 'None' in yield result: %s" % text
|
|
96
|
+
parts.append(text)
|
|
97
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Dict,
|
|
7
|
+
List,
|
|
8
|
+
Optional,
|
|
9
|
+
Set,
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from annet.storage import Device, Storage
|
|
14
|
+
from .base import TreeGenerator, _filter_str
|
|
15
|
+
from .exceptions import NotSupportedDevice
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JSONFragment(TreeGenerator):
|
|
19
|
+
"""Generates parts of JSON config file."""
|
|
20
|
+
|
|
21
|
+
TYPE = "JSON_FRAGMENT"
|
|
22
|
+
TAGS: List[str] = []
|
|
23
|
+
|
|
24
|
+
def __init__(self, storage: Storage):
|
|
25
|
+
super().__init__()
|
|
26
|
+
self.storage = storage
|
|
27
|
+
self._json_config: Dict[str, Any] = {}
|
|
28
|
+
self._config_pointer: List[str] = []
|
|
29
|
+
|
|
30
|
+
# if two generators edit same file, commands from generator with greater `reload_prio` will be used
|
|
31
|
+
if not hasattr(self, "reload_prio"):
|
|
32
|
+
self.reload_prio = 100
|
|
33
|
+
|
|
34
|
+
def supports_device(self, device: Device):
|
|
35
|
+
return bool(self.path(device))
|
|
36
|
+
|
|
37
|
+
def path(self, device: Device) -> Optional[str]:
|
|
38
|
+
raise NotImplementedError("Required PATH for JSON_FRAGMENT generator")
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def get_aliases(cls) -> Set[str]:
|
|
42
|
+
return {cls.__name__, *cls.TAGS}
|
|
43
|
+
|
|
44
|
+
def acl(self, device: Device) -> Union[str, List[str]]:
|
|
45
|
+
"""
|
|
46
|
+
Restrict the generator to a specified ACL using JSON Pointer syntax.
|
|
47
|
+
|
|
48
|
+
Expected ACL to be a list of strings, but a single string is also allowed.
|
|
49
|
+
"""
|
|
50
|
+
raise NotImplementedError("Required ACL for JSON_FRAGMENT generator")
|
|
51
|
+
|
|
52
|
+
def acl_safe(self, device: Device) -> Union[str, List[str]]:
|
|
53
|
+
"""
|
|
54
|
+
Restrict the generator to a specified safe ACL using JSON Pointer syntax.
|
|
55
|
+
|
|
56
|
+
Expected ACL to be a list of strings, but a single string is also allowed.
|
|
57
|
+
"""
|
|
58
|
+
raise NotImplementedError("Required ACL for JSON_FRAGMENT generator")
|
|
59
|
+
|
|
60
|
+
def run(self, device: Device):
|
|
61
|
+
raise NotImplementedError
|
|
62
|
+
|
|
63
|
+
def get_reload_cmds(self, device: Device) -> str:
|
|
64
|
+
ret = self.reload(device) or ""
|
|
65
|
+
return ret
|
|
66
|
+
|
|
67
|
+
def reload(self, device) -> Optional[str]:
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
@contextlib.contextmanager
|
|
71
|
+
def block(self, *tokens, indent=None): # pylint: disable=unused-argument
|
|
72
|
+
block_str = "".join(map(_filter_str, tokens))
|
|
73
|
+
self._config_pointer.append(block_str)
|
|
74
|
+
try:
|
|
75
|
+
yield
|
|
76
|
+
finally:
|
|
77
|
+
self._config_pointer.pop()
|
|
78
|
+
|
|
79
|
+
@contextlib.contextmanager
|
|
80
|
+
def block_piped(self, *tokens, indent=None): # pylint: disable=unused-argument
|
|
81
|
+
block_str = "|".join(map(_filter_str, tokens))
|
|
82
|
+
self._config_pointer.append(block_str)
|
|
83
|
+
try:
|
|
84
|
+
yield
|
|
85
|
+
finally:
|
|
86
|
+
self._config_pointer.pop()
|
|
87
|
+
|
|
88
|
+
def __call__(self, device: Device, annotate: bool = False):
|
|
89
|
+
for cfg_fragment in self.run(device):
|
|
90
|
+
self._set_or_replace_dict(self._config_pointer, cfg_fragment)
|
|
91
|
+
return self._json_config
|
|
92
|
+
|
|
93
|
+
def _set_or_replace_dict(self, pointer, value):
|
|
94
|
+
if not pointer:
|
|
95
|
+
if self._json_config == {}:
|
|
96
|
+
self._json_config = value
|
|
97
|
+
else:
|
|
98
|
+
self._json_config = [self._json_config, value]
|
|
99
|
+
else:
|
|
100
|
+
self._set_dict(self._json_config, pointer, value)
|
|
101
|
+
|
|
102
|
+
@classmethod
|
|
103
|
+
def _to_str(cls, value: Any) -> str:
|
|
104
|
+
if isinstance(value, str):
|
|
105
|
+
return value
|
|
106
|
+
elif isinstance(value, list):
|
|
107
|
+
return [cls._to_str(x) for x in value]
|
|
108
|
+
elif isinstance(value, dict):
|
|
109
|
+
for k, v in value.items():
|
|
110
|
+
value[k] = cls._to_str(v)
|
|
111
|
+
return value
|
|
112
|
+
return str(value)
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def _set_dict(cls, cfg, pointer, value):
|
|
116
|
+
# pointer has at least one key
|
|
117
|
+
if len(pointer) == 1:
|
|
118
|
+
if pointer[0] in cfg:
|
|
119
|
+
cfg[pointer[0]] = [cfg[pointer[0]], cls._to_str(value)]
|
|
120
|
+
else:
|
|
121
|
+
cfg[pointer[0]] = cls._to_str(value)
|
|
122
|
+
else:
|
|
123
|
+
if pointer[0] not in cfg:
|
|
124
|
+
cfg[pointer[0]] = {}
|
|
125
|
+
cls._set_dict(cfg[pointer[0]], pointer[1:], cls._to_str(value))
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import (
|
|
5
|
+
Iterable,
|
|
6
|
+
List,
|
|
7
|
+
Set,
|
|
8
|
+
Union,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
from annet.lib import (
|
|
12
|
+
add_annotation,
|
|
13
|
+
flatten,
|
|
14
|
+
)
|
|
15
|
+
from .base import TreeGenerator, _filter_str
|
|
16
|
+
from .exceptions import NotSupportedDevice
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class PartialGenerator(TreeGenerator):
|
|
20
|
+
TYPE = "PARTIAL"
|
|
21
|
+
TAGS: List[str] = []
|
|
22
|
+
|
|
23
|
+
def __init__(self, storage):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.storage = storage
|
|
26
|
+
self._annotate = False
|
|
27
|
+
self._running_gen = None
|
|
28
|
+
self._annotations = []
|
|
29
|
+
self._annotation_module = self.__class__.__module__ or ""
|
|
30
|
+
|
|
31
|
+
def supports_device(self, device) -> bool:
|
|
32
|
+
if self.__class__.run is PartialGenerator.run:
|
|
33
|
+
return bool(self._get_vendor_func(device.hw.vendor, "run"))
|
|
34
|
+
else:
|
|
35
|
+
return True
|
|
36
|
+
|
|
37
|
+
def acl(self, device):
|
|
38
|
+
acl_func = self._get_vendor_func(device.hw.vendor, "acl")
|
|
39
|
+
if acl_func:
|
|
40
|
+
return acl_func(device)
|
|
41
|
+
|
|
42
|
+
def acl_safe(self, device):
|
|
43
|
+
acl_func = self._get_vendor_func(device.hw.vendor, "acl_safe")
|
|
44
|
+
if acl_func:
|
|
45
|
+
return acl_func(device)
|
|
46
|
+
|
|
47
|
+
def run(self, device) -> Iterable[Union[str, tuple]]:
|
|
48
|
+
run_func = self._get_vendor_func(device.hw.vendor, "run")
|
|
49
|
+
if run_func:
|
|
50
|
+
return run_func(device)
|
|
51
|
+
|
|
52
|
+
def get_user_runner(self, device):
|
|
53
|
+
if self.__class__.run is not PartialGenerator.run:
|
|
54
|
+
return self.run
|
|
55
|
+
return self._get_vendor_func(device.hw.vendor, "run")
|
|
56
|
+
|
|
57
|
+
def _get_vendor_func(self, vendor: str, func_name: str):
|
|
58
|
+
attr_name = f"{func_name}_{vendor}"
|
|
59
|
+
if hasattr(self, attr_name):
|
|
60
|
+
return getattr(self, attr_name)
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
# =====
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def get_aliases(cls) -> Set[str]:
|
|
67
|
+
return {cls.__name__, *cls.TAGS}
|
|
68
|
+
|
|
69
|
+
def __call__(self, device, annotate=False):
|
|
70
|
+
self._indents = []
|
|
71
|
+
self._rows = []
|
|
72
|
+
self._running_gen = self.run(device)
|
|
73
|
+
self._annotate = annotate
|
|
74
|
+
|
|
75
|
+
if annotate and self.__class__.__module__:
|
|
76
|
+
self._annotation_module = ".".join(
|
|
77
|
+
self.__class__.__module__.split(".")[-2:])
|
|
78
|
+
|
|
79
|
+
for text in self._running_gen:
|
|
80
|
+
if isinstance(text, tuple):
|
|
81
|
+
text = " ".join(map(_filter_str, flatten(text)))
|
|
82
|
+
else:
|
|
83
|
+
text = _filter_str(text)
|
|
84
|
+
self._append_text(text)
|
|
85
|
+
|
|
86
|
+
for row in self._rows:
|
|
87
|
+
assert re.search(r"\bNone\b", row) is None, \
|
|
88
|
+
"Found 'None' in yield result: %s" % (row)
|
|
89
|
+
if annotate:
|
|
90
|
+
generated_rows = (
|
|
91
|
+
add_annotation(x, y)
|
|
92
|
+
for (x, y) in zip(self._rows, self._annotations)
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
generated_rows = self._rows
|
|
96
|
+
return "\n".join(generated_rows) + "\n"
|
|
97
|
+
|
|
98
|
+
def _append_text(self, text):
|
|
99
|
+
def annotation_cb(row):
|
|
100
|
+
annotation = "%s:%d" % self.get_running_line()
|
|
101
|
+
self._annotations.append(annotation)
|
|
102
|
+
return row
|
|
103
|
+
|
|
104
|
+
self._append_text_cb(
|
|
105
|
+
text,
|
|
106
|
+
annotation_cb if self._annotate else None
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def get_running_line(self):
|
|
110
|
+
if not self._running_gen or not self._running_gen.gi_frame:
|
|
111
|
+
return (repr(self._running_gen), -1)
|
|
112
|
+
return self._annotation_module, self._running_gen.gi_frame.f_lineno
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def literal(cls, item):
|
|
116
|
+
return '"{}"'.format(item)
|
|
117
|
+
|
|
118
|
+
def __repr__(self):
|
|
119
|
+
return "<%s>" % self.__class__.__name__
|
annet/generators/perf.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import (
|
|
5
|
+
Optional,
|
|
6
|
+
Union,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from annet import tracing
|
|
10
|
+
from annet.tracing import tracing_connector
|
|
11
|
+
from annet.types import (
|
|
12
|
+
GeneratorPartialRunArgs,
|
|
13
|
+
GeneratorPerf,
|
|
14
|
+
)
|
|
15
|
+
from .entire import Entire
|
|
16
|
+
from .partial import PartialGenerator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GeneratorPerfMesurer:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
gen: Union[PartialGenerator, Entire],
|
|
23
|
+
run_args: Optional[GeneratorPartialRunArgs] = None,
|
|
24
|
+
trace_min_duration: tracing.MinDurationT = None
|
|
25
|
+
):
|
|
26
|
+
self._gen = gen
|
|
27
|
+
self._run_args = run_args
|
|
28
|
+
|
|
29
|
+
self._start_time: float = 0.0
|
|
30
|
+
self._span_ctx = None
|
|
31
|
+
self._span = None
|
|
32
|
+
self._trace_min_duration = trace_min_duration
|
|
33
|
+
|
|
34
|
+
self.last_result: Optional[GeneratorPerf] = None
|
|
35
|
+
|
|
36
|
+
def start(self) -> None:
|
|
37
|
+
self.last_result = None
|
|
38
|
+
|
|
39
|
+
self._span_ctx = tracing_connector.get().start_as_current_span(
|
|
40
|
+
"gen:call",
|
|
41
|
+
tracer_name=self._gen.__class__.__module__,
|
|
42
|
+
min_duration=self._trace_min_duration,
|
|
43
|
+
)
|
|
44
|
+
self._span = self._span_ctx.__enter__() # pylint: disable=unnecessary-dunder-call
|
|
45
|
+
|
|
46
|
+
if self._span:
|
|
47
|
+
self._span.set_attributes(
|
|
48
|
+
{"generator.class": self._gen.__class__.__name__})
|
|
49
|
+
if self._run_args:
|
|
50
|
+
tracing_connector.get().set_device_attributes(
|
|
51
|
+
self._span, self._run_args.device,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self._start_time = time.monotonic()
|
|
55
|
+
|
|
56
|
+
def finish(self, exc_type=None, exc_val=None,
|
|
57
|
+
exc_tb=None) -> GeneratorPerf:
|
|
58
|
+
total = time.monotonic() - self._start_time
|
|
59
|
+
self._span_ctx.__exit__(exc_type, exc_val, exc_tb)
|
|
60
|
+
|
|
61
|
+
meta = {}
|
|
62
|
+
if tracing_connector.get().enabled:
|
|
63
|
+
span_context = self._span.get_span_context()
|
|
64
|
+
meta = {
|
|
65
|
+
"span": {
|
|
66
|
+
"trace_id": str(span_context.trace_id),
|
|
67
|
+
"span_id": str(span_context.span_id),
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
self.last_result = GeneratorPerf(total=total, rt=total, meta=meta)
|
|
72
|
+
return self.last_result
|
|
73
|
+
|
|
74
|
+
def __enter__(self):
|
|
75
|
+
self.start()
|
|
76
|
+
return self
|
|
77
|
+
|
|
78
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
79
|
+
self.finish(exc_type, exc_val, exc_tb)
|
annet/generators/ref.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .partial import PartialGenerator
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RefGenerator(PartialGenerator):
|
|
5
|
+
def __init__(self, storage, groups=None):
|
|
6
|
+
super().__init__(storage)
|
|
7
|
+
self.groups = groups
|
|
8
|
+
|
|
9
|
+
def ref(self, device):
|
|
10
|
+
if hasattr(self, "ref_" + device.hw.vendor):
|
|
11
|
+
return getattr(self, "ref_" + device.hw.vendor)(device)
|
|
12
|
+
return ""
|
|
13
|
+
|
|
14
|
+
def with_groups(self, groups):
|
|
15
|
+
return type(self)(self.storage, groups)
|