|
17 | 17 |
|
18 | 18 | import ast |
19 | 19 | import re |
20 | | -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional |
| 20 | +from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional, Tuple |
21 | 21 |
|
22 | 22 | from codeanalyzer.schema.py_schema import PyEntrypoint |
23 | 23 |
|
24 | 24 | if TYPE_CHECKING: |
25 | | - from codeanalyzer.entrypoints.rules import DecoratorRule |
| 25 | + from codeanalyzer.entrypoints.rules import BaseRule, DecoratorRule |
26 | 26 |
|
27 | 27 | # Dispatch names that are HTTP verbs. DRF's ViewSet dispatch names |
28 | 28 | # (list, retrieve, create, ...) are NOT verbs and must not be emitted as such. |
@@ -132,3 +132,52 @@ def entrypoints_from_decorators( |
132 | 132 | ) |
133 | 133 | ) |
134 | 134 | return out |
| 135 | + |
| 136 | + |
| 137 | +def entrypoints_from_bases( |
| 138 | + cls, |
| 139 | + framework: str, |
| 140 | + rules: Iterable["BaseRule"], |
| 141 | + ruleset: str, |
| 142 | + resolve: Callable[[str], Optional[str]], |
| 143 | +) -> Tuple[List[PyEntrypoint], Dict[str, List[PyEntrypoint]]]: |
| 144 | + """Records for a routed class and for the methods the framework dispatches. |
| 145 | +
|
| 146 | + ``resolve`` maps a written base-class name to its resolved qualified name |
| 147 | + (identity when already qualified). Dispatch names are intersected with the |
| 148 | + methods the class actually defines, so a ``ListView`` with only ``get`` |
| 149 | + gains no phantom ``post`` entrypoint. |
| 150 | + """ |
| 151 | + class_eps: List[PyEntrypoint] = [] |
| 152 | + method_eps: Dict[str, List[PyEntrypoint]] = {} |
| 153 | + |
| 154 | + for rule in rules: |
| 155 | + if not any( |
| 156 | + match_pattern(rule.match, resolve(b) or b) for b in (cls.base_classes or []) |
| 157 | + ): |
| 158 | + continue |
| 159 | + class_eps.append( |
| 160 | + PyEntrypoint( |
| 161 | + framework=framework, |
| 162 | + confidence=rule.confidence, |
| 163 | + rule=rule.id, |
| 164 | + ruleset=ruleset, |
| 165 | + evidence=cls.signature, |
| 166 | + ) |
| 167 | + ) |
| 168 | + defined = set((cls.callables or {}).keys()) |
| 169 | + for name in rule.dispatch: |
| 170 | + if name not in defined: |
| 171 | + continue |
| 172 | + method_eps.setdefault(name, []).append( |
| 173 | + PyEntrypoint( |
| 174 | + framework=framework, |
| 175 | + confidence=rule.confidence, |
| 176 | + rule=f"{rule.id}.dispatch", |
| 177 | + ruleset=ruleset, |
| 178 | + evidence=cls.signature, |
| 179 | + http_methods=[name.upper()] if name in _HTTP_VERBS else [], |
| 180 | + via=cls.id or None, |
| 181 | + ) |
| 182 | + ) |
| 183 | + return class_eps, method_eps |
0 commit comments