Coverage for archdocs/features/messaging_queue/parser.py: 100%
25 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 00:00 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 00:00 +0000
1import re as py_re
2import types
3import typing
5from archdocs import settings
6from archdocs.features.messaging_queue import const
9_SUBSCRIBER_DECORATOR_RE: typing.Final = py_re.compile(r"@\w+\.subscriber\(", flags=settings.TYPICAL_RE_FLAGS)
10_PRODUCER_RE: typing.Final = py_re.compile(
11 r"(?:@\w+\.(?:publisher|producer)|\w+\.publish)\(",
12 flags=settings.TYPICAL_RE_FLAGS,
13)
14_BROKER_PATTERNS: typing.Final = types.MappingProxyType(
15 {
16 one_broker: py_re.compile(
17 rf"\bfaststream\.{one_broker.value}\b",
18 flags=settings.TYPICAL_RE_FLAGS,
19 )
20 for one_broker in const.BrokersEnum
21 },
22)
23_BROKER_VARIABLE_PATTERN: typing.Final = py_re.compile(
24 r"(?P<variable>\w+)\s*(?::[^=\n]+)?=\s*(?P<broker_class>\w+)\s*\(",
25 flags=settings.TYPICAL_RE_FLAGS,
26)
27_TOPIC_PATTERNS_OF_DIRECTION: typing.Final = types.MappingProxyType(
28 {
29 const.MessageDirection.consumed: (
30 py_re.compile(
31 r"@(?P<variable>\w+)\.subscriber\(\s*[\"'](?P<topic>[^\"']+)[\"']",
32 flags=settings.TYPICAL_RE_FLAGS,
33 ),
34 ),
35 const.MessageDirection.produced: (
36 py_re.compile(
37 r"@(?P<variable>\w+)\.publisher\(\s*[\"'](?P<topic>[^\"']+)[\"']",
38 flags=settings.TYPICAL_RE_FLAGS,
39 ),
40 py_re.compile(
41 r"(?P<variable>\w+)\.publish\([^()]*?\b(?:"
42 + "|".join(const.DESTINATION_KEYWORDS)
43 + r")\s*=\s*[\"'](?P<topic>[^\"']+)[\"']",
44 flags=settings.TYPICAL_RE_FLAGS,
45 ),
46 ),
47 },
48)
49_BROKER_NAME_OF_CLASS: typing.Final = types.MappingProxyType(
50 {one_broker_class: one_broker.value for one_broker, one_broker_class in const.BROKER_CLASS_OF_NAME.items()},
51)
52_EMPTY_FEATURES: typing.Final = const.MQFeatures()
55def _collect_broker_of_variable(raw_source: str, /) -> dict[str, str]:
56 return {
57 one_match.group("variable"): _BROKER_NAME_OF_CLASS[one_match.group("broker_class")]
58 for one_match in _BROKER_VARIABLE_PATTERN.finditer(raw_source)
59 if one_match.group("broker_class") in _BROKER_NAME_OF_CLASS
60 }
63def _collect_topics_of_broker(
64 raw_source: str,
65 broker_of_variable: dict[str, str],
66 message_direction: const.MessageDirection,
67 broker_name: str,
68 /,
69) -> tuple[str, ...]:
70 return tuple(
71 dict.fromkeys(
72 one_match.group("topic")
73 for one_pattern in _TOPIC_PATTERNS_OF_DIRECTION[message_direction]
74 for one_match in one_pattern.finditer(raw_source)
75 if broker_of_variable.get(one_match.group("variable")) == broker_name
76 ),
77 )
80def _build_broker_flow(raw_source: str, broker_of_variable: dict[str, str], broker_name: str, /) -> const.BrokerFlow:
81 return const.BrokerFlow(
82 broker_name=broker_name,
83 consumes=bool(_SUBSCRIBER_DECORATOR_RE.search(raw_source)),
84 produces=bool(_PRODUCER_RE.search(raw_source)),
85 consumed_topics=_collect_topics_of_broker(
86 raw_source,
87 broker_of_variable,
88 const.MessageDirection.consumed,
89 broker_name,
90 ),
91 produced_topics=_collect_topics_of_broker(
92 raw_source,
93 broker_of_variable,
94 const.MessageDirection.produced,
95 broker_name,
96 ),
97 )
100def find_faststream_features(raw_source: str) -> const.MQFeatures:
101 if "faststream" not in raw_source:
102 return _EMPTY_FEATURES
103 if not _SUBSCRIBER_DECORATOR_RE.search(raw_source) and not _PRODUCER_RE.search(raw_source):
104 return _EMPTY_FEATURES
105 broker_of_variable: typing.Final = _collect_broker_of_variable(raw_source)
106 return const.MQFeatures(
107 broker_flows=tuple(
108 _build_broker_flow(raw_source, broker_of_variable, one_broker.value)
109 for one_broker, one_pattern in _BROKER_PATTERNS.items()
110 if one_pattern.search(raw_source)
111 ),
112 )