Skip to content

Commit

Permalink
chore(integrations): move fastapi,flask,flask_cache,futures to intern…
Browse files Browse the repository at this point in the history
…al (#10127)

- Moves all integration internals in ddtrace/contrib/(integration name)/
to ddtrace/contrib/internal/(integration name)/ for fasapi, flask,
flask_cache, and futures
- Ensures ddtrace/contrib/(integration name)/ and
ddtrace/contrib/(integration name)/ continue to expose the same
functions, classes, imports, and module level variables (via from
..internal.integration.module import * imports).
- Log a deprecation warning if internal modules in
ddtrace/contrib/(integration name)/ and ddtrace/contrib/(integration
name)/. Only patch and unpack methods should be exposed by these
packages.
- #9996

## Checklist
- [x] PR author has checked that all the criteria below are met
- The PR description includes an overview of the change
- The PR description articulates the motivation for the change
- The change includes tests OR the PR description describes a testing
strategy
- The PR description notes risks associated with the change, if any
- Newly-added code is easy to change
- The change follows the [library release note
guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html)
- The change includes or references documentation updates if necessary
- Backport labels are set (if
[applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting))

## Reviewer Checklist
- [ ] Reviewer has checked that all the criteria below are met 
- Title is accurate
- All changes are related to the pull request's stated goal
- Avoids breaking
[API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces)
changes
- Testing strategy adequately addresses listed risks
- Newly-added code is easy to change
- Release note makes sense to a user of the library
- If necessary, author has acknowledged and discussed the performance
implications of this PR as reported in the benchmarks PR comment
- Backport labels are set in a manner that is consistent with the
[release branch maintenance
policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)

---------

Co-authored-by: Emmett Butler <[email protected]>
  • Loading branch information
rachelyangdog and emmettbutler authored Aug 9, 2024
1 parent 5d8e52a commit 6f46cc7
Show file tree
Hide file tree
Showing 21 changed files with 1,183 additions and 1,093 deletions.
10 changes: 7 additions & 3 deletions ddtrace/contrib/fastapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,12 @@

with require_modules(required_modules) as missing_modules:
if not missing_modules:
from .patch import get_version
from .patch import patch
from .patch import unpatch
# Required to allow users to import from `ddtrace.contrib.fastapi.patch` directly
from . import patch as _ # noqa: F401, I001

# Expose public methods
from ..internal.fastapi.patch import get_version
from ..internal.fastapi.patch import patch
from ..internal.fastapi.patch import unpatch

__all__ = ["patch", "unpatch", "get_version"]
103 changes: 2 additions & 101 deletions ddtrace/contrib/fastapi/patch.py
Original file line number Diff line number Diff line change
@@ -1,103 +1,4 @@
import os
from ..internal.fastapi.patch import * # noqa: F401,F403

import fastapi
import fastapi.routing

from ddtrace import Pin
from ddtrace import config
from ddtrace.contrib.asgi.middleware import TraceMiddleware
from ddtrace.contrib.starlette.patch import _trace_background_tasks
from ddtrace.contrib.starlette.patch import traced_handler
from ddtrace.internal.logger import get_logger
from ddtrace.internal.schema import schematize_service_name
from ddtrace.internal.utils.wrappers import unwrap as _u
from ddtrace.vendor.wrapt import ObjectProxy
from ddtrace.vendor.wrapt import wrap_function_wrapper as _w


log = get_logger(__name__)

config._add(
"fastapi",
dict(
_default_service=schematize_service_name("fastapi"),
request_span_name="fastapi.request",
distributed_tracing=True,
trace_query_string=None, # Default to global config
_trace_asgi_websocket=os.getenv("DD_ASGI_TRACE_WEBSOCKET", default=False),
),
)


def get_version():
# type: () -> str
return getattr(fastapi, "__version__", "")


def wrap_middleware_stack(wrapped, instance, args, kwargs):
return TraceMiddleware(app=wrapped(*args, **kwargs), integration_config=config.fastapi)


async def traced_serialize_response(wrapped, instance, args, kwargs):
"""Wrapper for fastapi.routing.serialize_response function.
This function is called on all non-Response objects to
convert them to a serializable form.
This is the wrapper which calls ``jsonable_encoder``.
This function does not do the actual encoding from
obj -> json string (e.g. json.dumps()). That is handled
by the Response.render function.
DEV: We do not wrap ``jsonable_encoder`` because it calls
itself recursively, so there is a chance the overhead
added by creating spans will be higher than desired for
the result.
"""
pin = Pin.get_from(fastapi)
if not pin or not pin.enabled():
return await wrapped(*args, **kwargs)

with pin.tracer.trace("fastapi.serialize_response"):
return await wrapped(*args, **kwargs)


def patch():
if getattr(fastapi, "_datadog_patch", False):
return

fastapi._datadog_patch = True
Pin().onto(fastapi)
_w("fastapi.applications", "FastAPI.build_middleware_stack", wrap_middleware_stack)
_w("fastapi.routing", "serialize_response", traced_serialize_response)

if not isinstance(fastapi.BackgroundTasks.add_task, ObjectProxy):
_w("fastapi", "BackgroundTasks.add_task", _trace_background_tasks(fastapi))

# We need to check that Starlette instrumentation hasn't already patched these
if not isinstance(fastapi.routing.APIRoute.handle, ObjectProxy):
_w("fastapi.routing", "APIRoute.handle", traced_handler)

if not isinstance(fastapi.routing.Mount.handle, ObjectProxy):
_w("starlette.routing", "Mount.handle", traced_handler)


def unpatch():
if not getattr(fastapi, "_datadog_patch", False):
return

fastapi._datadog_patch = False

_u(fastapi.applications.FastAPI, "build_middleware_stack")
_u(fastapi.routing, "serialize_response")

# We need to check that Starlette instrumentation hasn't already unpatched these
if isinstance(fastapi.routing.APIRoute.handle, ObjectProxy):
_u(fastapi.routing.APIRoute, "handle")

if isinstance(fastapi.routing.Mount.handle, ObjectProxy):
_u(fastapi.routing.Mount, "handle")

if isinstance(fastapi.BackgroundTasks.add_task, ObjectProxy):
_u(fastapi.BackgroundTasks, "add_task")
# TODO: deprecate and remove this module
9 changes: 4 additions & 5 deletions ddtrace/contrib/flask/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,9 @@ def index():
with require_modules(required_modules) as missing_modules:
if not missing_modules:
# DEV: We do this so we can `@mock.patch('ddtrace.contrib.flask._patch.<func>')` in tests
from . import patch as _patch

patch = _patch.patch
unpatch = _patch.unpatch
get_version = _patch.get_version
from . import patch as _ # noqa: F401, I001
from ..internal.flask.patch import patch
from ..internal.flask.patch import unpatch
from ..internal.flask.patch import get_version

__all__ = ["patch", "unpatch", "get_version"]
Loading

0 comments on commit 6f46cc7

Please sign in to comment.