Commit bbc788a
fix(debugger): patch Django URLPattern.callback so DI breakpoints fire (#776)
## Problem
Dynamic Instrumentation (DI) breakpoints set on Django view functions
silently never fire. Django's `path()` / `re_path()` / `include()`
constructors store the view function as a **direct object reference** on
`URLPattern.callback` at module-import time
(`django/urls/resolvers.py`). When DI replaces the function on its
defining module via `setattr(views_module, "my_view", wrapper)`, the URL
resolver tree still holds the pre-wrap reference. At request time,
Django's `URLResolver.resolve()` returns the stale callback and the
original (unwrapped) function executes — no snapshot, no
instrumentation.
This is the same failure mode the existing `_patch_flask_view_functions`
patcher solves for Flask's `app.view_functions` dict, just for a
different framework's internal cache.
## Fix
Add a parallel Django code path mirroring the Flask patcher's structure:
- **`_patch_django_url_patterns`** (entry point): import-guarded against
missing Django; reaches the active resolver tree via
`django.urls.get_resolver(None)` (the authoritative root resolver Django
uses at request time), with a belt-and-suspenders module scan for
unconfigured / test-only contexts.
- **`_patch_single_resolver`**: recursive walker through
`URLResolver.url_patterns` to handle `include()`-nested children.
`visited: set[int]` of `id(resolver)` for cycle protection.
- **`_maybe_patch_pattern`**: atomic mutator. Identity match first, then
`__name__` + `__module__` fallback (handles `functools.wraps`-preserving
decorators like `@login_required` / `@csrf_exempt` and OTel
auto-instrumentation wrappers).
Wired into `_patch_framework_references` after the Flask call. A failure
in either framework patcher is logged at DEBUG and never blocks the
other.
## Why this discovery strategy
The common Django layout is **cross-module**: `urlpatterns` lives in a
dedicated `urls.py` while views live in `views.py`. The `module` arg the
wrapper passes to `_patch_framework_references` is the **views** module,
which has no `urlpatterns`. Flask's "scan `dir(module)` only" pattern is
insufficient — we have to reach into Django's global resolver via
`get_resolver(None)`. The defensive module scan covers
configured-but-not-yet-resolved cases.
## Class-based views — no special handling needed
Django's `View.dispatch()` re-resolves methods via `getattr(self,
method_name)` at request time. The closure returned by `as_view()`
captures `cls` once, but method lookup is dynamic on every request.
Patching `MyView.get` via the existing class-method `setattr` path is
sufficient — the closure's stale `cls` cell is irrelevant. The patcher
does NOT mutate `as_view()` closure cells; the closure has `__name__ ==
"view"` (not the user's view name), so it's naturally excluded from the
name+module fallback.
## Tests
`tests/.../test_function_wrapper_django.py` (NEW, 15 tests, mirrors
Flask test layout):
- Identity match
- Name+module fallback (handles `@functools.wraps`-style wrapping)
- No Django installed (no-op)
- No urlpatterns in module
- Function not in url patterns
- **`include()`-nested resolvers** (recursive descent)
- **`get_resolver(None)` cross-module discovery** (the common Django
layout)
- **Decorator-wrapped view** (`functools.wraps`-preserving)
- **CBV unaffected** (locks in the design decision: closure is not
mutated)
- Resolver-traversal-error swallowed
- `get_resolver` failure swallowed (module-scan fallback)
- `_replace_function_in_module` integration
- `restore_function` integration
- Dispatcher delegation
- Exception swallowing
`tests/.../test_function_wrapper_branches.py` — added
`TestPatchDjangoUrlPatternsErrors` (7 tests) for defensive `except`
branches: import-guard, `get_resolver` failure, `dir(module)` failure,
per-attribute `getattr` failure, malformed resolver, `callback` getter
raises, `callback` setter raises.
## Validation
| Python | Tests passing | New tests |
|---|---|---|
| 3.9 | 544 passed, 29 skipped | +22 (15 Django + 7 branches) |
| 3.10 | 544 passed, 29 skipped | +22 |
| 3.11 | 544 passed, 29 skipped | +22 |
| 3.12 | 548 passed, 25 skipped | +22 |
- `black` / `isort` / `flake8` / `pylint` (10.00/10) / `codespell`:
clean
- No changes to existing Flask patcher; existing Flask tests unchanged
## Test plan
- [x] Unit tests pass on Python 3.9 / 3.10 / 3.11 / 3.12
- [x] Linters clean (black, isort, flake8, pylint, codespell)
- [ ] E2E: configure DI probe on a Django view in the sample app,
confirm snapshot lands in CW Logs
---------
Co-authored-by: Prashant Srivastava <srprash@amazon.com>1 parent eab5ee3 commit bbc788a
17 files changed
Lines changed: 1797 additions & 5 deletions
File tree
- aws-opentelemetry-distro
- src/amazon/opentelemetry/distro/debugger
- tests/amazon/opentelemetry/distro/debugger
- contract-tests
- images/applications/di-django
- api
- di_django_server
- tests/test/amazon/di
Lines changed: 224 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
796 | 796 | | |
797 | 797 | | |
798 | 798 | | |
| 799 | + | |
| 800 | + | |
799 | 801 | | |
800 | 802 | | |
801 | 803 | | |
802 | 804 | | |
803 | 805 | | |
804 | 806 | | |
805 | 807 | | |
| 808 | + | |
| 809 | + | |
806 | 810 | | |
807 | 811 | | |
808 | 812 | | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
809 | 821 | | |
810 | 822 | | |
811 | | - | |
812 | | - | |
813 | | - | |
| 823 | + | |
| 824 | + | |
814 | 825 | | |
815 | 826 | | |
816 | 827 | | |
| |||
845 | 856 | | |
846 | 857 | | |
847 | 858 | | |
848 | | - | |
| 859 | + | |
849 | 860 | | |
850 | 861 | | |
851 | 862 | | |
852 | | - | |
| 863 | + | |
853 | 864 | | |
854 | 865 | | |
855 | 866 | | |
| |||
903 | 914 | | |
904 | 915 | | |
905 | 916 | | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
906 | 1125 | | |
907 | 1126 | | |
908 | 1127 | | |
| |||
0 commit comments