-
Notifications
You must be signed in to change notification settings - Fork 48
fix(django): make middleware truly hybrid-compatible with sync and async Django stacks #348
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR fixes the Django middleware to support both sync (WSGI) and async (ASGI) applications by implementing a true hybrid pattern. The previous async-only approach (PR #328) broke compatibility with sync middleware chains. This change restores the process_exception method that was removed, fixing exception capture regression (issue #329).
Key Changes:
- Implements hybrid middleware pattern using conditional routing in
__call__to__acall__for async paths - Restores
process_exceptionmethod for exception capturing in both sync and async modes - Adds comprehensive test coverage for sync, async, and hybrid middleware behavior
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| posthog/integrations/django.py | Refactored middleware to support hybrid sync/async operation and restored exception processing |
| posthog/test/integrations/test_middleware.py | Added comprehensive test suite covering sync, async, and hybrid middleware scenarios |
| CHANGELOG.md | Documented the middleware fix and exception capture restoration |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3 files reviewed, 1 comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ync Django stacks Address code review feedback and critical issues from PR #328. Changes: - Keep __call__ as sync method that conditionally routes to __acall__ for async paths - Use markcoroutinefunction() to properly mark instances when async is detected - Detect async/sync at init time via iscoroutinefunction(get_response) - Remove process_exception method - it was non-functional (Django doesn't call it on new-style middleware without MiddlewareMixin) - Fix markcoroutinefunction fallback to be a simple no-op instead of accessing private API - Exception capture works correctly via contexts.new_context() which has built-in exception handling - Add comprehensive test coverage for sync, async, and hybrid middleware behavior - Add async exception capture tests - Refactor tests to use proper middleware initialization This implementation follows Django's recommended hybrid middleware pattern where both sync_capable and async_capable are True, allowing Django to pass requests without conversion while the middleware adapts based on the detected mode. The sync path behavior is identical to version 6.7.4 (pre-async), ensuring perfect backward compatibility for WSGI deployments. Addresses #329 Related to #328
36275f0 to
8c2d4e7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works both in WSGI and ASGI. Thank you!
Restores the process_exception method that was removed in v6.7.5 (PR #328), which broke exception capture from Django views and downstream middleware. Django converts view exceptions into responses before they propagate through the middleware stack's __call__ method, so the context manager's exception handler never sees them. Django provides these exceptions via the process_exception hook instead. Changes: - Add process_exception method to capture exceptions from views and downstream middleware with proper request context and tags - Add tests verifying process_exception behavior and settings (capture_exceptions, request_filter) - Update version to 6.7.11 - Update CHANGELOG to accurately reflect the fix Context: - v6.7.4 and earlier: no process_exception - exceptions not captured (#286) - v6.7.4 fix: added process_exception - worked correctly - v6.7.5 (PR #328): removed process_exception when adding async - broke again (#329) - v6.7.10 (PR #348): attempted fix but didn't restore process_exception - still broken - v6.7.11 (this fix): restores process_exception - fixes #329 Fixes #329
Restores the process_exception method that was removed in v6.7.5 (PR #328), which broke exception capture from Django views and downstream middleware. Django converts view exceptions into responses before they propagate through the middleware stack's __call__ method, so the context manager's exception handler never sees them. Django provides these exceptions via the process_exception hook instead. Changes: - Add process_exception method to capture exceptions from views and downstream middleware with proper request context and tags - Add tests verifying process_exception behavior and settings (capture_exceptions, request_filter) - Update version to 6.7.11 - Update CHANGELOG to accurately reflect the fix Context: - v6.7.4 and earlier: no process_exception - exceptions not captured (#286) - v6.7.4 fix: added process_exception - worked correctly - v6.7.5 (PR #328): removed process_exception when adding async - broke again (#329) - v6.7.10 (PR #348): attempted fix but didn't restore process_exception - still broken - v6.7.11 (this fix): restores process_exception - fixes #329 Fixes #329
Restores the process_exception method that was removed in v6.7.5 (PR #328), which broke exception capture from Django views and downstream middleware. Django converts view exceptions into responses before they propagate through the middleware stack's __call__ method, so the context manager's exception handler never sees them. Django provides these exceptions via the process_exception hook instead. Changes: - Add process_exception method to capture exceptions from views and downstream middleware with proper request context and tags - Add tests verifying process_exception behavior and settings (capture_exceptions, request_filter) - Update version to 6.7.11 - Update CHANGELOG to accurately reflect the fix Context: - v6.7.4 and earlier: no process_exception - exceptions not captured (#286) - v6.7.4 fix: added process_exception - worked correctly - v6.7.5 (PR #328): removed process_exception when adding async - broke again (#329) - v6.7.10 (PR #348): attempted fix but didn't restore process_exception - still broken - v6.7.11 (this fix): restores process_exception - fixes #329 Fixes #329
|
This PR claimed process_exception was "non-functional without MiddlewareMixin" and removed it, expecting the context manager to handle exception capture. That assessment was incorrect. Django's process_exception works on all middleware styles and is required to capture view exceptions. Django's BaseHandler intercepts view exceptions before they reach middleware's context manager, providing them only via the process_exception hook. Without it, view exceptions are silently lost. Properly fixed in #350 with process_exception restored and tests that correctly demonstrate Django's exception interception behavior. |
Fixes SynchronousOnlyOperation when accessing request.user in ASGI deployments. Django's request.user is a lazy object that triggers DB access when touched. In async context, this raises SynchronousOnlyOperation. The middleware now uses request.auser() in async paths to avoid blocking calls. Changes: - Add aextract_tags() and aextract_request_user() methods - Update __acall__() to use async versions - Add test verifying user extraction works in async context - Follow Django's naming convention for async methods (auser, asave, etc.) The issue was introduced in v6.7.5 (PR #328) but only became apparent after v6.7.10 (PR #348) made ASGI functional enough for users to discover it. Fixes #355
Fixes SynchronousOnlyOperation when accessing request.user in ASGI deployments. Django's request.user is a lazy object that triggers DB access when touched. In async context, this raises SynchronousOnlyOperation. The middleware now uses request.auser() in async paths to avoid blocking calls. Changes: - Add aextract_tags() and aextract_request_user() methods - Update __acall__() to use async versions - Add test verifying user extraction works in async context - Follow Django's naming convention for async methods (auser, asave, etc.) The issue was introduced in v6.7.5 (PR #328) but only became apparent after v6.7.10 (PR #348) made ASGI functional enough for users to discover it. Fixes #355
Fixes SynchronousOnlyOperation when accessing request.user in ASGI deployments. Django's request.user is a lazy object that triggers DB access when touched. In async context, this raises SynchronousOnlyOperation. The middleware now uses request.auser() in async paths to avoid blocking calls. Changes: - Add aextract_tags() and aextract_request_user() methods - Update __acall__() to use async versions - Add test verifying user extraction works in async context - Follow Django's naming convention for async methods (auser, asave, etc.) The issue was introduced in v6.7.5 (PR #328) but only became apparent after v6.7.10 (PR #348) made ASGI functional enough for users to discover it. Fixes #355
Summary
Make PostHog Django middleware truly hybrid, compatible with both sync (WSGI) and async (ASGI) Django applications without forcing sync-only deployments into async mode.
Context
PR #328 introduced async middleware support but inadvertently broke sync-only Django setups by setting both capability flags, which forces Django to treat the entire middleware chain as async. This breaks compatibility with sync middlewares like sessions and authentication.
Changes
Middleware Implementation
__call__as sync method that conditionally routes to__acall__for async pathsmarkcoroutinefunction()to properly mark middleware instances when async is detectediscoroutinefunction(get_response)process_exceptionmethod - it was non-functional (Django doesn't call it on new-style middleware withoutMiddlewareMixin)contexts.new_context(capture_exceptions=True)which has built-in exception handlingCode Quality Improvements
markcoroutinefunctionfallback to be a no-op instead of accessing private API (addresses Copilot review comment)Technical Details
This follows Django's recommended hybrid middleware pattern where:
sync_capable = Trueandasync_capable = Trueare set__call__remains synchronous but routes to async path when neededmarkcoroutinefunction()when async mode detectedThe sync path behavior (lines 198-206) is identical to version 6.7.4 (pre-async), ensuring perfect backward compatibility for WSGI deployments.
Test Results
Acceptance Criteria
Addresses #329
Related to #328