Skip to content

Commit

Permalink
Merge branch 'master' into byk/tests/stable-import-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
BYK authored Sep 23, 2024
2 parents 540edc6 + 26b86a5 commit 7d6d5a7
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 31 deletions.
27 changes: 23 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,14 @@ jobs:
# This will also trigger "make dist" that creates the Python packages
make aws-lambda-layer
- name: Upload Python Packages
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ github.sha }}
name: artifact-build_lambda_layer
path: |
dist/*
if-no-files-found: 'error'
# since this artifact will be merged, compression is not necessary
compression-level: '0'

docs:
name: Build SDK API Doc
Expand All @@ -91,7 +94,23 @@ jobs:
make apidocs
cd docs/_build && zip -r gh-pages ./
- uses: actions/[email protected]
- uses: actions/upload-artifact@v4
with:
name: artifact-docs
path: |
docs/_build/gh-pages.zip
if-no-files-found: 'error'
# since this artifact will be merged, compression is not necessary
compression-level: '0'

merge:
name: Create Release Artifact
runs-on: ubuntu-latest
needs: [build_lambda_layer, docs]
steps:
- uses: actions/upload-artifact/merge@v4
with:
# Craft expects release assets from github to be a single artifact named after the sha.
name: ${{ github.sha }}
path: docs/_build/gh-pages.zip
pattern: artifact-*
delete-merged: true
8 changes: 7 additions & 1 deletion sentry_sdk/integrations/_wsgi_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,13 @@ def json(self):
if not self.is_json():
return None

raw_data = self.raw_data()
try:
raw_data = self.raw_data()
except (RawPostDataException, ValueError):
# The body might have already been read, in which case this will
# fail
raw_data = None

if raw_data is None:
return None

Expand Down
12 changes: 11 additions & 1 deletion sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,17 @@ async def sentry_app_handle(self, request, *args, **kwargs):
# have no way to tell. Do not set span status.
reraise(*_capture_exception())

transaction.set_http_status(response.status)
try:
# A valid response handler will return a valid response with a status. But, if the handler
# returns an invalid response (e.g. None), the line below will raise an AttributeError.
# Even though this is likely invalid, we need to handle this case to ensure we don't break
# the application.
response_status = response.status
except AttributeError:
pass
else:
transaction.set_http_status(response_status)

return response

Application._handle = sentry_app_handle
Expand Down
9 changes: 7 additions & 2 deletions sentry_sdk/integrations/cohere.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,19 @@
from cohere import (
ChatStreamEndEvent,
NonStreamedChatResponse,
StreamedChatResponse_StreamEnd,
)

if TYPE_CHECKING:
from cohere import StreamedChatResponse
except ImportError:
raise DidNotEnable("Cohere not installed")

try:
# cohere 5.9.3+
from cohere import StreamEndStreamedChatResponse
except ImportError:
from cohere import StreamedChatResponse_StreamEnd as StreamEndStreamedChatResponse


COLLECTED_CHAT_PARAMS = {
"model": SPANDATA.AI_MODEL_ID,
Expand Down Expand Up @@ -189,7 +194,7 @@ def new_iterator():
with capture_internal_exceptions():
for x in old_iterator:
if isinstance(x, ChatStreamEndEvent) or isinstance(
x, StreamedChatResponse_StreamEnd
x, StreamEndStreamedChatResponse
):
collect_chat_response_fields(
span,
Expand Down
2 changes: 1 addition & 1 deletion sentry_sdk/integrations/langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ def new_configure(*args, **kwargs):
elif isinstance(existing_callbacks, BaseCallbackHandler):
new_callbacks.append(existing_callbacks)
else:
logger.warn("Unknown callback type: %s", existing_callbacks)
logger.debug("Unknown callback type: %s", existing_callbacks)

already_added = False
for callback in new_callbacks:
Expand Down
5 changes: 3 additions & 2 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ def start_transaction(
transaction=None,
instrumenter=INSTRUMENTER.SENTRY,
custom_sampling_context=None,
**kwargs
**kwargs,
):
# type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan]
"""
Expand Down Expand Up @@ -1324,7 +1324,8 @@ def _apply_breadcrumbs_to_event(self, event, hint, options):
crumb["timestamp"] = datetime_from_isoformat(crumb["timestamp"])

event["breadcrumbs"]["values"].sort(key=lambda crumb: crumb["timestamp"])
except Exception:
except Exception as err:
logger.debug("Error when sorting breadcrumbs", exc_info=err)
pass

def _apply_user_to_event(self, event, hint, options):
Expand Down
22 changes: 19 additions & 3 deletions sentry_sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,29 @@ def format_timestamp(value):
return utctime.strftime("%Y-%m-%dT%H:%M:%S.%fZ")


ISO_TZ_SEPARATORS = frozenset(("+", "-"))


def datetime_from_isoformat(value):
# type: (str) -> datetime
try:
return datetime.fromisoformat(value)
except AttributeError:
result = datetime.fromisoformat(value)
except (AttributeError, ValueError):
# py 3.6
return datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f")
timestamp_format = (
"%Y-%m-%dT%H:%M:%S.%f" if "." in value else "%Y-%m-%dT%H:%M:%S"
)
if value.endswith("Z"):
value = value[:-1] + "+0000"

if value[-6] in ISO_TZ_SEPARATORS:
timestamp_format += "%z"
value = value[:-3] + value[-2:]
elif value[-5] in ISO_TZ_SEPARATORS:
timestamp_format += "%z"

result = datetime.strptime(value, timestamp_format)
return result.astimezone(timezone.utc)


def event_hint_with_exc_info(exc_info=None):
Expand Down
21 changes: 21 additions & 0 deletions tests/integrations/aiohttp/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,3 +596,24 @@ async def hello(request):
(event,) = events
assert event["contexts"]["trace"]["origin"] == "auto.http.aiohttp"
assert event["spans"][0]["origin"] == "auto.http.aiohttp"


@pytest.mark.asyncio
@pytest.mark.parametrize("invalid_response", (None, "invalid"))
async def test_invalid_response(
sentry_init, aiohttp_client, capture_events, invalid_response
):
sentry_init(integrations=[AioHttpIntegration()])

async def handler(_):
return invalid_response

app = web.Application()
app.router.add_get("/", handler)

client = await aiohttp_client(app)

# Invalid response should result on a ServerDisconnectedError in the client side, not an internal server error.
# Important to note that the ServerDisconnectedError indicates we have no error server-side.
with pytest.raises(ServerDisconnectedError):
await client.get("/")
2 changes: 1 addition & 1 deletion tests/integrations/django/myapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def middleware(request):
except (ImportError, KeyError):
from sentry_sdk.utils import logger

logger.warn("No psycopg2 found, testing with SQLite.")
logger.warning("No psycopg2 found, testing with SQLite.")


# Password validation
Expand Down
28 changes: 27 additions & 1 deletion tests/integrations/django/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import re
import pytest
from functools import partial
from unittest.mock import patch

from werkzeug.test import Client

from django import VERSION as DJANGO_VERSION
from django.contrib.auth.models import User
from django.core.management import execute_from_command_line
from django.db.utils import OperationalError, ProgrammingError, DataError
from django.http.request import RawPostDataException

try:
from django.urls import reverse
Expand All @@ -20,7 +22,11 @@
from sentry_sdk._compat import PY310
from sentry_sdk import capture_message, capture_exception
from sentry_sdk.consts import SPANDATA
from sentry_sdk.integrations.django import DjangoIntegration, _set_db_data
from sentry_sdk.integrations.django import (
DjangoIntegration,
DjangoRequestExtractor,
_set_db_data,
)
from sentry_sdk.integrations.django.signals_handlers import _get_receiver_name
from sentry_sdk.integrations.executing import ExecutingIntegration
from sentry_sdk.tracing import Span
Expand Down Expand Up @@ -740,6 +746,26 @@ def test_read_request(sentry_init, client, capture_events):
assert "data" not in event["request"]


def test_request_body_already_read(sentry_init, client, capture_events):
sentry_init(integrations=[DjangoIntegration()])

events = capture_events()

class MockExtractor(DjangoRequestExtractor):
def raw_data(self):
raise RawPostDataException

with patch("sentry_sdk.integrations.django.DjangoRequestExtractor", MockExtractor):
client.post(
reverse("post_echo"), data=b'{"hey": 42}', content_type="application/json"
)

(event,) = events

assert event["message"] == "hi"
assert "data" not in event["request"]


def test_template_tracing_meta(sentry_init, client, capture_events):
sentry_init(integrations=[DjangoIntegration()])
events = capture_events()
Expand Down
39 changes: 24 additions & 15 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import pytest
from sentry_sdk.client import Client
from sentry_sdk.utils import datetime_from_isoformat
from tests.conftest import patch_start_tracing_child

import sentry_sdk
Expand Down Expand Up @@ -401,11 +402,12 @@ def test_breadcrumbs(sentry_init, capture_events):
def test_breadcrumb_ordering(sentry_init, capture_events):
sentry_init()
events = capture_events()
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)

timestamps = [
datetime.datetime.now() - datetime.timedelta(days=10),
datetime.datetime.now() - datetime.timedelta(days=8),
datetime.datetime.now() - datetime.timedelta(days=12),
now - datetime.timedelta(days=10),
now - datetime.timedelta(days=8),
now - datetime.timedelta(days=12),
]

for timestamp in timestamps:
Expand All @@ -421,41 +423,48 @@ def test_breadcrumb_ordering(sentry_init, capture_events):

assert len(event["breadcrumbs"]["values"]) == len(timestamps)
timestamps_from_event = [
datetime.datetime.strptime(
x["timestamp"].replace("Z", ""), "%Y-%m-%dT%H:%M:%S.%f"
)
for x in event["breadcrumbs"]["values"]
datetime_from_isoformat(x["timestamp"]) for x in event["breadcrumbs"]["values"]
]
assert timestamps_from_event == sorted(timestamps)


def test_breadcrumb_ordering_different_types(sentry_init, capture_events):
sentry_init()
events = capture_events()
now = datetime.datetime.now(datetime.timezone.utc)

timestamps = [
datetime.datetime.now() - datetime.timedelta(days=10),
datetime.datetime.now() - datetime.timedelta(days=8),
datetime.datetime.now() - datetime.timedelta(days=12),
now - datetime.timedelta(days=10),
now - datetime.timedelta(days=8),
now.replace(microsecond=0) - datetime.timedelta(days=12),
now - datetime.timedelta(days=9),
now - datetime.timedelta(days=13),
now.replace(microsecond=0) - datetime.timedelta(days=11),
]

breadcrumb_timestamps = [
timestamps[0],
timestamps[1].isoformat(),
datetime.datetime.strftime(timestamps[2], "%Y-%m-%dT%H:%M:%S") + "Z",
datetime.datetime.strftime(timestamps[3], "%Y-%m-%dT%H:%M:%S.%f") + "+00:00",
datetime.datetime.strftime(timestamps[4], "%Y-%m-%dT%H:%M:%S.%f") + "+0000",
datetime.datetime.strftime(timestamps[5], "%Y-%m-%dT%H:%M:%S.%f") + "-0000",
]

for i, timestamp in enumerate(timestamps):
add_breadcrumb(
message="Authenticated at %s" % timestamp,
category="auth",
level="info",
timestamp=timestamp if i % 2 == 0 else timestamp.isoformat(),
timestamp=breadcrumb_timestamps[i],
)

capture_exception(ValueError())
(event,) = events

assert len(event["breadcrumbs"]["values"]) == len(timestamps)
timestamps_from_event = [
datetime.datetime.strptime(
x["timestamp"].replace("Z", ""), "%Y-%m-%dT%H:%M:%S.%f"
)
for x in event["breadcrumbs"]["values"]
datetime_from_isoformat(x["timestamp"]) for x in event["breadcrumbs"]["values"]
]
assert timestamps_from_event == sorted(timestamps)

Expand Down
Loading

0 comments on commit 7d6d5a7

Please sign in to comment.