|
10 | 10 | - `get_response_headers`: Gets the response headers for the given exception. |
11 | 11 | """ |
12 | 12 |
|
13 | | -from typing import Dict, Union |
| 13 | +import importlib |
| 14 | +from typing import Callable, Dict, Union |
14 | 15 |
|
15 | 16 | from django.core.exceptions import ( |
16 | 17 | PermissionDenied, |
|
23 | 24 | from drf_simple_api_errors import extra_handlers |
24 | 25 | from drf_simple_api_errors.settings import api_settings |
25 | 26 |
|
| 27 | +DEFAULT_EXTRA_HANDLERS = [ |
| 28 | + extra_handlers.set_default_detail_to_formatted_exc_default_code |
| 29 | +] |
| 30 | +"""Default extra handlers to always apply.""" |
| 31 | + |
26 | 32 |
|
27 | 33 | def apply_extra_handlers(exc: Exception): |
28 | 34 | """ |
29 | 35 | Apply any extra exception handlers defined in the settings. |
30 | 36 |
|
31 | 37 | Args: |
32 | 38 | exc (Exception): The exception to handle. |
| 39 | +
|
| 40 | + Returns: |
| 41 | + int: The number of handlers applied (this is mainly for unit testing). |
33 | 42 | """ |
34 | 43 | # Get the default extra handlers and the ones defined in the settings. |
35 | 44 | # The default handlers are always applied to ensure that exceptions |
36 | 45 | # are formatted correctly. |
37 | | - default_extra_handlers = [ |
38 | | - extra_handlers.set_default_detail_to_formatted_exc_default_code |
39 | | - ] |
40 | | - settings_extra_handlers = api_settings.EXTRA_HANDLERS |
41 | | - |
42 | | - extra_handlers_to_apply = default_extra_handlers + settings_extra_handlers |
| 46 | + # Resolve the settings extra handlers. |
| 47 | + # The settings extra handlers is a list of strings representing |
| 48 | + # the import path to the handler function. |
| 49 | + # This allows for lazy loading of the handlers. |
| 50 | + settings_extra_handlers: list[Callable] = [] |
| 51 | + for handler_path in api_settings.EXTRA_HANDLERS or []: |
| 52 | + if not isinstance(handler_path, str): |
| 53 | + raise ValueError( |
| 54 | + f"EXTRA_HANDLERS must be a list of strings. Found: {type(handler_path)}" |
| 55 | + ) |
| 56 | + |
| 57 | + module_path, func_name = handler_path.rsplit(".", 1) |
| 58 | + try: |
| 59 | + module = importlib.import_module(module_path) |
| 60 | + except ModuleNotFoundError: |
| 61 | + raise ValueError(f"Path {handler_path} not found.") |
| 62 | + |
| 63 | + func = getattr(module, func_name, None) |
| 64 | + if func is None: |
| 65 | + raise ValueError(f"Handler {func_name} not found.") |
| 66 | + if not callable(func): |
| 67 | + raise ValueError(f"Handler {func_name} is not callable.") |
| 68 | + else: |
| 69 | + settings_extra_handlers.append(func) |
| 70 | + |
| 71 | + extra_handlers_to_apply = DEFAULT_EXTRA_HANDLERS + settings_extra_handlers |
43 | 72 | if extra_handlers_to_apply: |
44 | 73 | for handler in extra_handlers_to_apply: |
45 | 74 | handler(exc) |
46 | 75 |
|
| 76 | + return len(extra_handlers_to_apply) |
| 77 | + |
47 | 78 |
|
48 | 79 | def convert_django_exc_to_drf_api_exc( |
49 | 80 | exc: Exception, |
|
0 commit comments