|
2 | 2 | import importlib |
3 | 3 | import inspect |
4 | 4 | import textwrap |
| 5 | +import typing |
5 | 6 | from collections.abc import Callable, Generator, Iterable |
6 | 7 | from itertools import chain |
7 | 8 | from types import ModuleType |
@@ -56,6 +57,21 @@ def is_callable_instance(obj: object) -> bool: |
56 | 57 | return hasattr(obj, "__call__") and not isinstance(obj, type) |
57 | 58 |
|
58 | 59 |
|
| 60 | +def is_annotated_alias(annot): |
| 61 | + _AnnotatedAlias = getattr(typing, "_AnnotatedAlias") |
| 62 | + return isinstance(annot, _AnnotatedAlias) |
| 63 | + |
| 64 | + |
| 65 | +def get_annotated_dependency(annot): |
| 66 | + if is_annotated_alias(annot): |
| 67 | + annot_args = typing.get_args(annot) |
| 68 | + annot_dep = annot_args[1] if len(annot_args) > 1 else None |
| 69 | + if annot_dep and annot_dep.__class__.__name__ == "Depends": |
| 70 | + dependency = getattr(annot_dep, "dependency", None) |
| 71 | + return dependency |
| 72 | + return None |
| 73 | + |
| 74 | + |
59 | 75 | def create_exc_instance( |
60 | 76 | exc_class: type[Exception], exc_args: list[Any] | None = None |
61 | 77 | ) -> Exception | None: |
@@ -104,6 +120,8 @@ def extract_exceptions( |
104 | 120 | self.exceptions.extend(self.find_exceptions(function)) |
105 | 121 | if self.dependencyClasses: |
106 | 122 | self.exceptions += self.find_dependency_exceptions(route) |
| 123 | + if self.dependencyClasses: |
| 124 | + self.exceptions += self.find_annotated_dependency_exceptions(route) |
107 | 125 | if self.serviceClasses: |
108 | 126 | self.exceptions += self.find_service_exceptions(route) |
109 | 127 | return self.exceptions |
@@ -241,6 +259,35 @@ def find_dependency_exceptions( |
241 | 259 | exceptions.extend(_exceptions) |
242 | 260 | return exceptions |
243 | 261 |
|
| 262 | + def find_annotated_dependency_exceptions( |
| 263 | + self, |
| 264 | + route: APIRoute | Callable, |
| 265 | + ) -> list[HTTPException]: |
| 266 | + exceptions = [] |
| 267 | + assert self.dependencyClasses is not None |
| 268 | + func = route.endpoint if hasattr(route, "endpoint") else route |
| 269 | + module = inspect.getmodule(func) |
| 270 | + source = inspect.getsource(func) |
| 271 | + tree = ast.parse(source) |
| 272 | + |
| 273 | + for node in find_nodes(tree, (ast.FunctionDef, ast.AsyncFunctionDef)): |
| 274 | + for kwarg in getattr(node.args, "kwonlyargs", []): |
| 275 | + if kwarg and hasattr(kwarg, "annotation"): |
| 276 | + if ( |
| 277 | + annot := kwarg.annotation.id |
| 278 | + if hasattr(kwarg.annotation, "id") and kwarg.annotation.id |
| 279 | + else None |
| 280 | + ): |
| 281 | + cls = None |
| 282 | + try: |
| 283 | + cls = getattr(module, annot) |
| 284 | + except (AttributeError, NameError): |
| 285 | + ... |
| 286 | + if cls and (dependency := get_annotated_dependency(cls)): |
| 287 | + if _exceptions := self.find_exceptions(dependency, module): |
| 288 | + exceptions.extend(_exceptions) |
| 289 | + return exceptions |
| 290 | + |
244 | 291 | def create_exc_inst_from_raise_stmt( |
245 | 292 | self, |
246 | 293 | raise_stmt: ast.Raise, |
|
0 commit comments