Skip to content

Commit ce01379

Browse files
committed
Merge branch 'master' into ivana/toxgen/clean-more-stuff-up
2 parents b2bb64c + 13a8ae1 commit ce01379

File tree

19 files changed

+545
-20
lines changed

19 files changed

+545
-20
lines changed

scripts/populate_tox/populate_tox.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import functools
88
import hashlib
9+
import json
910
import os
1011
import sys
1112
import time
@@ -36,18 +37,20 @@
3637
# CUTOFF = datetime.now(tz=timezone.utc) - timedelta(days=365 * 5)
3738

3839
TOX_FILE = Path(__file__).resolve().parent.parent.parent / "tox.ini"
40+
RELEASES_CACHE_FILE = Path(__file__).resolve().parent / "releases.jsonl"
3941
ENV = Environment(
4042
loader=FileSystemLoader(Path(__file__).resolve().parent),
4143
trim_blocks=True,
4244
lstrip_blocks=True,
4345
)
4446

45-
PYPI_COOLDOWN = 0.1 # seconds to wait between requests to PyPI
47+
PYPI_COOLDOWN = 0.05 # seconds to wait between requests to PyPI
4648

4749
PYPI_PROJECT_URL = "https://pypi.python.org/pypi/{project}/json"
4850
PYPI_VERSION_URL = "https://pypi.python.org/pypi/{project}/{version}/json"
4951
CLASSIFIER_PREFIX = "Programming Language :: Python :: "
5052

53+
CACHE = defaultdict(dict)
5154

5255
IGNORE = {
5356
# Do not try auto-generating the tox entries for these. They will be
@@ -96,9 +99,33 @@ def fetch_package(package: str) -> Optional[dict]:
9699

97100
@functools.cache
98101
def fetch_release(package: str, version: Version) -> Optional[dict]:
99-
"""Fetch release metadata from PyPI."""
102+
"""Fetch release metadata from cache or, failing that, PyPI."""
103+
release = _fetch_from_cache(package, version)
104+
if release is not None:
105+
return release
106+
100107
url = PYPI_VERSION_URL.format(project=package, version=version)
101-
return fetch_url(url)
108+
release = fetch_url(url)
109+
if release is not None:
110+
_save_to_cache(package, version, release)
111+
return release
112+
113+
114+
def _fetch_from_cache(package: str, version: Version) -> Optional[dict]:
115+
package = _normalize_name(package)
116+
if package in CACHE and str(version) in CACHE[package]:
117+
CACHE[package][str(version)]["_accessed"] = True
118+
return CACHE[package][str(version)]
119+
120+
return None
121+
122+
123+
def _save_to_cache(package: str, version: Version, release: Optional[dict]) -> None:
124+
with open(RELEASES_CACHE_FILE, "a") as releases_cache:
125+
releases_cache.write(json.dumps(_normalize_release(release)) + "\n")
126+
127+
CACHE[_normalize_name(package)][str(version)] = release
128+
CACHE[_normalize_name(package)][str(version)]["_accessed"] = True
102129

103130

104131
def _prefilter_releases(
@@ -612,6 +639,24 @@ def get_last_updated() -> Optional[datetime]:
612639
return timestamp
613640

614641

642+
def _normalize_name(package: str) -> str:
643+
return package.lower().replace("-", "_")
644+
645+
646+
def _normalize_release(release: dict) -> dict:
647+
"""Filter out unneeded parts of the release JSON."""
648+
normalized = {
649+
"info": {
650+
"classifiers": release["info"]["classifiers"],
651+
"name": release["info"]["name"],
652+
"requires_python": release["info"]["requires_python"],
653+
"version": release["info"]["version"],
654+
"yanked": release["info"]["yanked"],
655+
},
656+
}
657+
return normalized
658+
659+
615660
def main(fail_on_changes: bool = False) -> None:
616661
"""
617662
Generate tox.ini from the tox.jinja template.
@@ -649,6 +694,20 @@ def main(fail_on_changes: bool = False) -> None:
649694
f"The SDK supports Python versions {MIN_PYTHON_VERSION} - {MAX_PYTHON_VERSION}."
650695
)
651696

697+
# Load file cache
698+
global CACHE
699+
700+
with open(RELEASES_CACHE_FILE) as releases_cache:
701+
for line in releases_cache:
702+
release = json.loads(line)
703+
name = _normalize_name(release["info"]["name"])
704+
version = release["info"]["version"]
705+
CACHE[name][version] = release
706+
CACHE[name][version][
707+
"_accessed"
708+
] = False # for cleaning up unused cache entries
709+
710+
# Process packages
652711
packages = defaultdict(list)
653712

654713
for group, integrations in GROUPS.items():
@@ -714,6 +773,21 @@ def main(fail_on_changes: bool = False) -> None:
714773
packages, update_timestamp=not fail_on_changes, last_updated=last_updated
715774
)
716775

776+
# Sort the release cache file
777+
releases = []
778+
with open(RELEASES_CACHE_FILE) as releases_cache:
779+
releases = [json.loads(line) for line in releases_cache]
780+
releases.sort(key=lambda r: (r["info"]["name"], r["info"]["version"]))
781+
with open(RELEASES_CACHE_FILE, "w") as releases_cache:
782+
for release in releases:
783+
if (
784+
CACHE[_normalize_name(release["info"]["name"])][
785+
release["info"]["version"]
786+
]["_accessed"]
787+
is True
788+
):
789+
releases_cache.write(json.dumps(release) + "\n")
790+
717791
if fail_on_changes:
718792
new_file_hash = get_file_hash()
719793
if old_file_hash != new_file_hash:

scripts/populate_tox/releases.jsonl

Lines changed: 236 additions & 0 deletions
Large diffs are not rendered by default.

sentry_sdk/consts.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,18 +765,20 @@ class SPANSTATUS:
765765
CANCELLED = "cancelled"
766766
DATA_LOSS = "data_loss"
767767
DEADLINE_EXCEEDED = "deadline_exceeded"
768+
ERROR = "error" # OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
768769
FAILED_PRECONDITION = "failed_precondition"
769770
INTERNAL_ERROR = "internal_error"
770771
INVALID_ARGUMENT = "invalid_argument"
771772
NOT_FOUND = "not_found"
772-
OK = "ok"
773+
OK = "ok" # HTTP 200 and OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
773774
OUT_OF_RANGE = "out_of_range"
774775
PERMISSION_DENIED = "permission_denied"
775776
RESOURCE_EXHAUSTED = "resource_exhausted"
776777
UNAUTHENTICATED = "unauthenticated"
777778
UNAVAILABLE = "unavailable"
778779
UNIMPLEMENTED = "unimplemented"
779780
UNKNOWN_ERROR = "unknown_error"
781+
UNSET = "unset" # OTel status code: https://opentelemetry.io/docs/concepts/signals/traces/#span-status
780782

781783

782784
class OP:

sentry_sdk/integrations/anthropic.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import sentry_sdk
55
from sentry_sdk.ai.monitoring import record_token_usage
66
from sentry_sdk.ai.utils import set_data_normalized, get_start_span_function
7-
from sentry_sdk.consts import OP, SPANDATA
7+
from sentry_sdk.consts import OP, SPANDATA, SPANSTATUS
88
from sentry_sdk.integrations import _check_minimum_version, DidNotEnable, Integration
99
from sentry_sdk.scope import should_send_default_pii
10+
from sentry_sdk.tracing_utils import set_span_errored
1011
from sentry_sdk.utils import (
1112
capture_internal_exceptions,
1213
event_from_exception,
@@ -52,6 +53,8 @@ def setup_once():
5253

5354
def _capture_exception(exc):
5455
# type: (Any) -> None
56+
set_span_errored()
57+
5558
event, hint = event_from_exception(
5659
exc,
5760
client_options=sentry_sdk.get_client().options,
@@ -357,7 +360,13 @@ def _sentry_patched_create_sync(*args, **kwargs):
357360
integration = sentry_sdk.get_client().get_integration(AnthropicIntegration)
358361
kwargs["integration"] = integration
359362

360-
return _execute_sync(f, *args, **kwargs)
363+
try:
364+
return _execute_sync(f, *args, **kwargs)
365+
finally:
366+
span = sentry_sdk.get_current_span()
367+
if span is not None and span.status == SPANSTATUS.ERROR:
368+
with capture_internal_exceptions():
369+
span.__exit__(None, None, None)
361370

362371
return _sentry_patched_create_sync
363372

@@ -390,6 +399,12 @@ async def _sentry_patched_create_async(*args, **kwargs):
390399
integration = sentry_sdk.get_client().get_integration(AnthropicIntegration)
391400
kwargs["integration"] = integration
392401

393-
return await _execute_async(f, *args, **kwargs)
402+
try:
403+
return await _execute_async(f, *args, **kwargs)
404+
finally:
405+
span = sentry_sdk.get_current_span()
406+
if span is not None and span.status == SPANSTATUS.ERROR:
407+
with capture_internal_exceptions():
408+
span.__exit__(None, None, None)
394409

395410
return _sentry_patched_create_async

sentry_sdk/integrations/cohere.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
from typing import TYPE_CHECKING
99

10+
from sentry_sdk.tracing_utils import set_span_errored
11+
1012
if TYPE_CHECKING:
1113
from typing import Any, Callable, Iterator
1214
from sentry_sdk.tracing import Span
@@ -84,6 +86,8 @@ def setup_once():
8486

8587
def _capture_exception(exc):
8688
# type: (Any) -> None
89+
set_span_errored()
90+
8791
event, hint = event_from_exception(
8892
exc,
8993
client_options=sentry_sdk.get_client().options,

sentry_sdk/integrations/huggingface_hub.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from sentry_sdk.consts import OP, SPANDATA
88
from sentry_sdk.integrations import DidNotEnable, Integration
99
from sentry_sdk.scope import should_send_default_pii
10+
from sentry_sdk.tracing_utils import set_span_errored
1011
from sentry_sdk.utils import (
1112
capture_internal_exceptions,
1213
event_from_exception,
@@ -52,6 +53,8 @@ def setup_once():
5253

5354
def _capture_exception(exc):
5455
# type: (Any) -> None
56+
set_span_errored()
57+
5558
event, hint = event_from_exception(
5659
exc,
5760
client_options=sentry_sdk.get_client().options,
@@ -127,8 +130,6 @@ def new_huggingface_task(*args, **kwargs):
127130
try:
128131
res = f(*args, **kwargs)
129132
except Exception as e:
130-
# Error Handling
131-
span.set_status("error")
132133
_capture_exception(e)
133134
span.__exit__(None, None, None)
134135
raise e from None

sentry_sdk/integrations/langchain.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from sentry_sdk.consts import OP, SPANDATA
99
from sentry_sdk.integrations import DidNotEnable, Integration
1010
from sentry_sdk.scope import should_send_default_pii
11-
from sentry_sdk.tracing import Span
12-
from sentry_sdk.tracing_utils import _get_value
11+
from sentry_sdk.tracing_utils import _get_value, set_span_errored
1312
from sentry_sdk.utils import logger, capture_internal_exceptions
1413

1514
from typing import TYPE_CHECKING
@@ -26,6 +25,7 @@
2625
Union,
2726
)
2827
from uuid import UUID
28+
from sentry_sdk.tracing import Span
2929

3030

3131
try:
@@ -116,7 +116,7 @@ def _handle_error(self, run_id, error):
116116

117117
span_data = self.span_map[run_id]
118118
span = span_data.span
119-
span.set_status("unknown")
119+
set_span_errored(span)
120120

121121
sentry_sdk.capture_exception(error, span.scope)
122122

sentry_sdk/integrations/openai.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from sentry_sdk.consts import SPANDATA
88
from sentry_sdk.integrations import DidNotEnable, Integration
99
from sentry_sdk.scope import should_send_default_pii
10+
from sentry_sdk.tracing_utils import set_span_errored
1011
from sentry_sdk.utils import (
1112
capture_internal_exceptions,
1213
event_from_exception,
@@ -83,6 +84,8 @@ def _capture_exception(exc, manual_span_cleanup=True):
8384
# Close an eventually open span
8485
# We need to do this by hand because we are not using the start_span context manager
8586
current_span = sentry_sdk.get_current_span()
87+
set_span_errored(current_span)
88+
8689
if manual_span_cleanup and current_span is not None:
8790
current_span.__exit__(None, None, None)
8891

sentry_sdk/integrations/openai_agents/spans/execute_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def update_execute_tool_span(span, agent, tool, result):
4242
if isinstance(result, str) and result.startswith(
4343
"An error occurred while running the tool"
4444
):
45-
span.set_status(SPANSTATUS.INTERNAL_ERROR)
45+
span.set_status(SPANSTATUS.ERROR)
4646

4747
if should_send_default_pii():
4848
span.set_data(SPANDATA.GEN_AI_TOOL_OUTPUT, result)

sentry_sdk/integrations/openai_agents/utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from sentry_sdk.consts import SPANDATA
44
from sentry_sdk.integrations import DidNotEnable
55
from sentry_sdk.scope import should_send_default_pii
6+
from sentry_sdk.tracing_utils import set_span_errored
67
from sentry_sdk.utils import event_from_exception, safe_serialize
78

89
from typing import TYPE_CHECKING
@@ -20,6 +21,8 @@
2021

2122
def _capture_exception(exc):
2223
# type: (Any) -> None
24+
set_span_errored()
25+
2326
event, hint = event_from_exception(
2427
exc,
2528
client_options=sentry_sdk.get_client().options,

0 commit comments

Comments
 (0)