tornadoweb / tornadoweb/tornado

Getting the templated path that led to an handler

Open
#3,616 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
22.2k
Forks
5.6k
Avg merge
3h 42m
Merged PRs (30d)
16

Description

For observability purpose in the tornado OpenTelemetry instrumentation we would like to get a templated path that led to an handler, e.g. with the following code:

class StoryHandler(RequestHandler):
    def get(self, story_id):
        self.write("this is story %s" % story_id)

app = Application([
    url(r"/story/([0-9]+)", StoryHandler, name="story")
    ])

We would like to being able to build something like /story/<story_id> starting from an handler instance, but let's ignore the regexp pattern to name problem and just think on how to get the rule. ATM I've drafted the following code:

from __future__ import annotations
from typing import Any
from tornado.routing import Router
from tornado.web import RequestHandler
from tornado import httputil

# Distinguishes "keep scanning sibling rules" from "matched a branch we can't
# inspect further, so stop and report no reliable result".
_NOT_FOUND = object()

def find_matched_rule(handler: RequestHandler):
  result = _find_rule(
      handler.application.default_router,
      handler.request,
      handler.__class__,
  )
  return None if result is _NOT_FOUND else result

def _find_rule(router: Any, request: httputil.HTTPServerRequest, handler_class: type[RequestHandler]):
  rules = getattr(router, "rules", None)
  if rules is None:
      # Opaque custom router; cannot inspect reliably.
      return _NOT_FOUND

  for rule in rules:
      params = rule.matcher.match(request)
      if params is None:
          continue
      target = getattr(rule, "target", None)
      if _is_handler_target(target):
          if target is handler_class:
              return rule
          # A different handler matched first, so Tornado would stop here too.
          return _NOT_FOUND
      if hasattr(target, "rules"):
          nested = _find_rule(target, request, handler_class)
          if nested is None:
              # Nested router did not resolve anything; keep scanning siblings.
              continue
          return nested
      if isinstance(target, Router):
          # Custom nested router matched, but we cannot see inside it.
          return _NOT_FOUND
      # Callable / connection delegate / other terminal target.
      return _NOT_FOUND
  return None

def _is_handler_target(target: Any) -> bool:
  return isinstance(target, type) and issubclass(target, RequestHandler)

So wondering if we're missing something or maybe we can get some help from tornado itself like a reference to the matched url in the handler. Thanks!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with tornado.routing.Router, tornado.web.RequestHandler, and httputil.HTTPServerRequest, then trace application.default_router and the rule matcher and target behavior described in the issue. Determine how a handler could reliably expose its matched route across nested and custom routers. Done means a supported way to obtain the matched rule or templated path, with behavior defined for unsupported router targets.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
50/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.