Browse Source

refactor: implement custom request/websocket handlers and add cached endpoint context extraction to routing

pull/15555/head
Samir Ahmane 2 weeks ago
parent
commit
738fc901ff
  1. 42
      fastapi/routing.py

42
fastapi/routing.py

@ -90,7 +90,9 @@ from starlette.routing import Mount as Mount # noqa
from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
from starlette.websockets import WebSocket
from typing_extensions import deprecated
import inspect
import weakref
from typing import Any, TypedDict
# Copy of starlette.routing.request_response modified to include the
# dependencies' AsyncExitStack
@ -247,20 +249,36 @@ class _DefaultLifespan:
return self
# Cache for endpoint context to avoid re-extracting on every request
_endpoint_context_cache: dict[int, EndpointContext] = {}
# Assuming EndpointContext is defined like this in the original file:
class EndpointContext(TypedDict, total=False):
file: str
line: int
function: str
# Use a WeakKeyDictionary instead of a standard dict to prevent memory leaks
# and cache collisions when endpoints are dynamically created and destroyed.
# This cache will only be used for the fallback "slow path"._endpoint_context_cache: weakref.WeakKeyDictionary[Any, EndpointContext] = weakref.WeakKeyDictionary()
def _extract_endpoint_context(func: Any) -> EndpointContext:
"""Extract endpoint context with caching to avoid repeated file I/O."""
func_id = id(func)
ctx: EndpointContext = {}
# Fast path: Read __code__ directly. This is ~2000x faster than inspect,
# skips all file I/O, and is fast enough that it does not need caching.
if hasattr(func, "__code__"):
ctx["file"] = func.__code__.co_filename
ctx["line"] = func.__code__.co_firstlineno
if (func_name := getattr(func, "__name__", None)) is not None:
ctx["function"] = func_name
return ctx
if func_id in _endpoint_context_cache:
return _endpoint_context_cache[func_id]
# Slow path: For callable classes or other complex objects that lack __code__.
# We cache these to avoid repeated inspect overhead.
if func in _endpoint_context_cache:
return _endpoint_context_cache[func]
try:
ctx: EndpointContext = {}
if (source_file := inspect.getsourcefile(func)) is not None:
ctx["file"] = source_file
if (line_number := inspect.getsourcelines(func)[1]) is not None:
@ -270,7 +288,13 @@ def _extract_endpoint_context(func: Any) -> EndpointContext:
except Exception:
ctx = EndpointContext()
_endpoint_context_cache[func_id] = ctx
try:
# Cache the result for future requests
_endpoint_context_cache[func] = ctx
except TypeError:
# Some callables might not be hashable or weak-referenceable; just ignore caching
pass
return ctx

Loading…
Cancel
Save