prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Instrument Starlette authentication
  Starlette has a basic authentication framework: https://www.starlette.io/authentication/
  
  We should probably copy what we do for Django here, tagging user ID's/ display names on traces.
  
  </issue>
  <code>
  [start of src/scout_apm/async_/starlette.py]
  1 # coding=utf-8
  2 from __future__ import absolute_import, division, print_function, unicode_literals
  3 
  4 import wrapt
  5 from starlette.background import BackgroundTask
  6 from starlette.requests import Request
  7 
  8 import scout_apm.core
  9 from scout_apm.core.tracked_request import TrackedRequest
  10 from scout_apm.core.web_requests import (
  11     create_filtered_path,
  12     ignore_path,
  13     track_amazon_request_queue_time,
  14     track_request_queue_time,
  15 )
  16 
  17 
  18 class ScoutMiddleware:
  19     def __init__(self, app):
  20         self.app = app
  21         installed = scout_apm.core.install()
  22         self._do_nothing = not installed
  23         if installed:
  24             install_background_instrumentation()
  25 
  26     async def __call__(self, scope, receive, send):
  27         if self._do_nothing or scope["type"] != "http":
  28             await self.app(scope, receive, send)
  29             return
  30 
  31         request = Request(scope)
  32         tracked_request = TrackedRequest.instance()
  33         # Can't name controller until post-routing - see final clause
  34         controller_span = tracked_request.start_span(operation="Controller/Unknown")
  35 
  36         tracked_request.tag(
  37             "path",
  38             create_filtered_path(request.url.path, request.query_params.multi_items()),
  39         )
  40         if ignore_path(request.url.path):
  41             tracked_request.tag("ignore_transaction", True)
  42 
  43         user_ip = (
  44             request.headers.get("x-forwarded-for", default="").split(",")[0]
  45             or request.headers.get("client-ip", default="").split(",")[0]
  46             or request.client.host
  47         )
  48         tracked_request.tag("user_ip", user_ip)
  49 
  50         queue_time = request.headers.get(
  51             "x-queue-start", default=""
  52         ) or request.headers.get("x-request-start", default="")
  53         tracked_queue_time = track_request_queue_time(queue_time, tracked_request)
  54         if not tracked_queue_time:
  55             amazon_queue_time = request.headers.get("x-amzn-trace-id", default="")
  56             track_amazon_request_queue_time(amazon_queue_time, tracked_request)
  57 
  58         def rename_controller_span_from_endpoint():
  59             if "endpoint" in scope:
  60                 # Rename top span
  61                 endpoint = scope["endpoint"]
  62                 controller_span.operation = "Controller/{}.{}".format(
  63                     endpoint.__module__, endpoint.__qualname__
  64                 )
  65                 tracked_request.is_real_request = True
  66 
  67         async def wrapped_send(data):
  68             # Finish HTTP span when body finishes sending, not later (e.g.
  69             # after background tasks)
  70             if data.get("type", None) == "http.response.body" and not data.get(
  71                 "more_body", False
  72             ):
  73                 rename_controller_span_from_endpoint()
  74                 tracked_request.stop_span()
  75             return await send(data)
  76 
  77         try:
  78             await self.app(scope, receive, wrapped_send)
  79         except Exception as exc:
  80             tracked_request.tag("error", "true")
  81             raise exc
  82         finally:
  83             if tracked_request.end_time is None:
  84                 rename_controller_span_from_endpoint()
  85                 tracked_request.stop_span()
  86 
  87 
  88 background_instrumentation_installed = False
  89 
  90 
  91 def install_background_instrumentation():
  92     global background_instrumentation_installed
  93     if background_instrumentation_installed:
  94         return
  95     background_instrumentation_installed = True
  96 
  97     @wrapt.decorator
  98     async def wrapped_background_call(wrapped, instance, args, kwargs):
  99         tracked_request = TrackedRequest.instance()
  100         tracked_request.is_real_request = True
  101         tracked_request.start_span(
  102             operation="Job/{}.{}".format(
  103                 instance.func.__module__, instance.func.__qualname__
  104             )
  105         )
  106         try:
  107             return await wrapped(*args, **kwargs)
  108         finally:
  109             tracked_request.stop_span()
  110 
  111     BackgroundTask.__call__ = wrapped_background_call(BackgroundTask.__call__)
  112
  [end of src/scout_apm/async_/starlette.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/src/scout_apm/async_/starlette.py b/src/scout_apm/async_/starlette.py
  --- a/src/scout_apm/async_/starlette.py
  +++ b/src/scout_apm/async_/starlette.py
  @@ -55,7 +55,7 @@
               amazon_queue_time = request.headers.get("x-amzn-trace-id", default="")
               track_amazon_request_queue_time(amazon_queue_time, tracked_request)
   
  -        def rename_controller_span_from_endpoint():
  +        def grab_extra_data():
               if "endpoint" in scope:
                   # Rename top span
                   endpoint = scope["endpoint"]
  @@ -64,13 +64,23 @@
                   )
                   tracked_request.is_real_request = True
   
  +            # From AuthenticationMiddleware - bypass request.user because it
  +            # throws AssertionError if 'user' is not in Scope, and we need a
  +            # try/except already
  +            try:
  +                username = scope["user"].display_name
  +            except (KeyError, AttributeError):
  +                pass
  +            else:
  +                tracked_request.tag("username", username)
  +
           async def wrapped_send(data):
               # Finish HTTP span when body finishes sending, not later (e.g.
               # after background tasks)
               if data.get("type", None) == "http.response.body" and not data.get(
                   "more_body", False
               ):
  -                rename_controller_span_from_endpoint()
  +                grab_extra_data()
                   tracked_request.stop_span()
               return await send(data)
   
  @@ -81,7 +91,7 @@
               raise exc
           finally:
               if tracked_request.end_time is None:
  -                rename_controller_span_from_endpoint()
  +                grab_extra_data()
                   tracked_request.stop_span()
  
verification_info: {"golden_diff": "diff --git a/src/scout_apm/async_/starlette.py b/src/scout_apm/async_/starlette.py\n--- a/src/scout_apm/async_/starlette.py\n+++ b/src/scout_apm/async_/starlette.py\n@@ -55,7 +55,7 @@\n             amazon_queue_time = request.headers.get(\"x-amzn-trace-id\", default=\"\")\n             track_amazon_request_queue_time(amazon_queue_time, tracked_request)\n \n-        def rename_controller_span_from_endpoint():\n+        def grab_extra_data():\n             if \"endpoint\" in scope:\n                 # Rename top span\n                 endpoint = scope[\"endpoint\"]\n@@ -64,13 +64,23 @@\n                 )\n                 tracked_request.is_real_request = True\n \n+            # From AuthenticationMiddleware - bypass request.user because it\n+            # throws AssertionError if 'user' is not in Scope, and we need a\n+            # try/except already\n+            try:\n+                username = scope[\"user\"].display_name\n+            except (KeyError, AttributeError):\n+                pass\n+            else:\n+                tracked_request.tag(\"username\", username)\n+\n         async def wrapped_send(data):\n             # Finish HTTP span when body finishes sending, not later (e.g.\n             # after background tasks)\n             if data.get(\"type\", None) == \"http.response.body\" and not data.get(\n                 \"more_body\", False\n             ):\n-                rename_controller_span_from_endpoint()\n+                grab_extra_data()\n                 tracked_request.stop_span()\n             return await send(data)\n \n@@ -81,7 +91,7 @@\n             raise exc\n         finally:\n             if tracked_request.end_time is None:\n-                rename_controller_span_from_endpoint()\n+                grab_extra_data()\n                 tracked_request.stop_span()\n", "issue": "Instrument Starlette authentication\nStarlette has a basic authentication framework: https://www.starlette.io/authentication/\r\n\r\nWe should probably copy what we do for Django here, tagging user ID's/ display names on traces.\n", "before_files": [{"content": "# coding=utf-8\nfrom __future__ import absolute_import, division, print_function, unicode_literals\n\nimport wrapt\nfrom starlette.background import BackgroundTask\nfrom starlette.requests import Request\n\nimport scout_apm.core\nfrom scout_apm.core.tracked_request import TrackedRequest\nfrom scout_apm.core.web_requests import (\n    create_filtered_path,\n    ignore_path,\n    track_amazon_request_queue_time,\n    track_request_queue_time,\n)\n\n\nclass ScoutMiddleware:\n    def __init__(self, app):\n        self.app = app\n        installed = scout_apm.core.install()\n        self._do_nothing = not installed\n        if installed:\n            install_background_instrumentation()\n\n    async def __call__(self, scope, receive, send):\n        if self._do_nothing or scope[\"type\"] != \"http\":\n            await self.app(scope, receive, send)\n            return\n\n        request = Request(scope)\n        tracked_request = TrackedRequest.instance()\n        # Can't name controller until post-routing - see final clause\n        controller_span = tracked_request.start_span(operation=\"Controller/Unknown\")\n\n        tracked_request.tag(\n            \"path\",\n            create_filtered_path(request.url.path, request.query_params.multi_items()),\n        )\n        if ignore_path(request.url.path):\n            tracked_request.tag(\"ignore_transaction\", True)\n\n        user_ip = (\n            request.headers.get(\"x-forwarded-for\", default=\"\").split(\",\")[0]\n            or request.headers.get(\"client-ip\", default=\"\").split(\",\")[0]\n            or request.client.host\n        )\n        tracked_request.tag(\"user_ip\", user_ip)\n\n        queue_time = request.headers.get(\n            \"x-queue-start\", default=\"\"\n        ) or request.headers.get(\"x-request-start\", default=\"\")\n        tracked_queue_time = track_request_queue_time(queue_time, tracked_request)\n        if not tracked_queue_time:\n            amazon_queue_time = request.headers.get(\"x-amzn-trace-id\", default=\"\")\n            track_amazon_request_queue_time(amazon_queue_time, tracked_request)\n\n        def rename_controller_span_from_endpoint():\n            if \"endpoint\" in scope:\n                # Rename top span\n                endpoint = scope[\"endpoint\"]\n                controller_span.operation = \"Controller/{}.{}\".format(\n                    endpoint.__module__, endpoint.__qualname__\n                )\n                tracked_request.is_real_request = True\n\n        async def wrapped_send(data):\n            # Finish HTTP span when body finishes sending, not later (e.g.\n            # after background tasks)\n            if data.get(\"type\", None) == \"http.response.body\" and not data.get(\n                \"more_body\", False\n            ):\n                rename_controller_span_from_endpoint()\n                tracked_request.stop_span()\n            return await send(data)\n\n        try:\n            await self.app(scope, receive, wrapped_send)\n        except Exception as exc:\n            tracked_request.tag(\"error\", \"true\")\n            raise exc\n        finally:\n            if tracked_request.end_time is None:\n                rename_controller_span_from_endpoint()\n                tracked_request.stop_span()\n\n\nbackground_instrumentation_installed = False\n\n\ndef install_background_instrumentation():\n    global background_instrumentation_installed\n    if background_instrumentation_installed:\n        return\n    background_instrumentation_installed = True\n\n    @wrapt.decorator\n    async def wrapped_background_call(wrapped, instance, args, kwargs):\n        tracked_request = TrackedRequest.instance()\n        tracked_request.is_real_request = True\n        tracked_request.start_span(\n            operation=\"Job/{}.{}\".format(\n                instance.func.__module__, instance.func.__qualname__\n            )\n        )\n        try:\n            return await wrapped(*args, **kwargs)\n        finally:\n            tracked_request.stop_span()\n\n    BackgroundTask.__call__ = wrapped_background_call(BackgroundTask.__call__)\n", "path": "src/scout_apm/async_/starlette.py"}]}