Skip to content

Commit 9d5bdab

Browse files
committed
Merge branch 'master' into potel-base
2 parents cc90218 + 6000f87 commit 9d5bdab

File tree

9 files changed

+133
-34
lines changed

9 files changed

+133
-34
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
* @getsentry/team-web-sdk-backend
1+
* @getsentry/owners-python-sdk

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- name: Get auth token
2222
id: token
23-
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
23+
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
2424
with:
2525
app-id: ${{ vars.SENTRY_RELEASE_BOT_CLIENT_ID }}
2626
private-key: ${{ secrets.SENTRY_RELEASE_BOT_PRIVATE_KEY }}

scripts/populate_tox/populate_tox.py

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import time
1010
from bisect import bisect_left
1111
from collections import defaultdict
12-
from datetime import datetime, timezone
12+
from datetime import datetime, timedelta, timezone # noqa: F401
1313
from importlib.metadata import metadata
1414
from packaging.specifiers import SpecifierSet
1515
from packaging.version import Version
@@ -29,13 +29,19 @@
2929
from split_tox_gh_actions.split_tox_gh_actions import GROUPS
3030

3131

32+
# Set CUTOFF this to a datetime to ignore packages older than CUTOFF
33+
CUTOFF = None
34+
# CUTOFF = datetime.now(tz=timezone.utc) - timedelta(days=365 * 5)
35+
3236
TOX_FILE = Path(__file__).resolve().parent.parent.parent / "tox.ini"
3337
ENV = Environment(
3438
loader=FileSystemLoader(Path(__file__).resolve().parent),
3539
trim_blocks=True,
3640
lstrip_blocks=True,
3741
)
3842

43+
PYPI_COOLDOWN = 0.15 # seconds to wait between requests to PyPI
44+
3945
PYPI_PROJECT_URL = "https://pypi.python.org/pypi/{project}/json"
4046
PYPI_VERSION_URL = "https://pypi.python.org/pypi/{project}/{version}/json"
4147
CLASSIFIER_PREFIX = "Programming Language :: Python :: "
@@ -88,27 +94,34 @@
8894
}
8995

9096

91-
@functools.cache
92-
def fetch_package(package: str) -> dict:
93-
"""Fetch package metadata from PyPI."""
94-
url = PYPI_PROJECT_URL.format(project=package)
95-
pypi_data = requests.get(url)
97+
def fetch_url(url: str) -> Optional[dict]:
98+
for attempt in range(3):
99+
pypi_data = requests.get(url)
96100

97-
if pypi_data.status_code != 200:
98-
print(f"{package} not found")
101+
if pypi_data.status_code == 200:
102+
return pypi_data.json()
99103

100-
return pypi_data.json()
104+
backoff = PYPI_COOLDOWN * 2**attempt
105+
print(
106+
f"{url} returned an error: {pypi_data.status_code}. Attempt {attempt + 1}/3. Waiting {backoff}s"
107+
)
108+
time.sleep(backoff)
109+
110+
return None
101111

102112

103113
@functools.cache
104-
def fetch_release(package: str, version: Version) -> dict:
105-
url = PYPI_VERSION_URL.format(project=package, version=version)
106-
pypi_data = requests.get(url)
114+
def fetch_package(package: str) -> Optional[dict]:
115+
"""Fetch package metadata from PyPI."""
116+
url = PYPI_PROJECT_URL.format(project=package)
117+
return fetch_url(url)
107118

108-
if pypi_data.status_code != 200:
109-
print(f"{package} not found")
110119

111-
return pypi_data.json()
120+
@functools.cache
121+
def fetch_release(package: str, version: Version) -> Optional[dict]:
122+
"""Fetch release metadata from PyPI."""
123+
url = PYPI_VERSION_URL.format(project=package, version=version)
124+
return fetch_url(url)
112125

113126

114127
def _prefilter_releases(
@@ -153,9 +166,13 @@ def _prefilter_releases(
153166
if meta["yanked"]:
154167
continue
155168

156-
if older_than is not None:
157-
if datetime.fromisoformat(meta["upload_time_iso_8601"]) > older_than:
158-
continue
169+
uploaded = datetime.fromisoformat(meta["upload_time_iso_8601"])
170+
171+
if older_than is not None and uploaded > older_than:
172+
continue
173+
174+
if CUTOFF is not None and uploaded < CUTOFF:
175+
continue
159176

160177
version = Version(release)
161178

@@ -229,8 +246,14 @@ def get_supported_releases(
229246
expected_python_versions = SpecifierSet(f">={MIN_PYTHON_VERSION}")
230247

231248
def _supports_lowest(release: Version) -> bool:
232-
time.sleep(0.1) # don't DoS PYPI
233-
py_versions = determine_python_versions(fetch_release(package, release))
249+
time.sleep(PYPI_COOLDOWN) # don't DoS PYPI
250+
251+
pypi_data = fetch_release(package, release)
252+
if pypi_data is None:
253+
print("Failed to fetch necessary data from PyPI. Aborting.")
254+
sys.exit(1)
255+
256+
py_versions = determine_python_versions(pypi_data)
234257
target_python_versions = TEST_SUITE_CONFIG[integration].get("python")
235258
if target_python_versions:
236259
target_python_versions = SpecifierSet(target_python_versions)
@@ -499,7 +522,11 @@ def _add_python_versions_to_release(
499522
integration: str, package: str, release: Version
500523
) -> None:
501524
release_pypi_data = fetch_release(package, release)
502-
time.sleep(0.1) # give PYPI some breathing room
525+
if release_pypi_data is None:
526+
print("Failed to fetch necessary data from PyPI. Aborting.")
527+
sys.exit(1)
528+
529+
time.sleep(PYPI_COOLDOWN) # give PYPI some breathing room
503530

504531
target_python_versions = TEST_SUITE_CONFIG[integration].get("python")
505532
if target_python_versions:
@@ -592,6 +619,9 @@ def main(fail_on_changes: bool = False) -> None:
592619

593620
# Fetch data for the main package
594621
pypi_data = fetch_package(package)
622+
if pypi_data is None:
623+
print("Failed to fetch necessary data from PyPI. Aborting.")
624+
sys.exit(1)
595625

596626
# Get the list of all supported releases
597627

sentry_sdk/client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from sentry_sdk.tracing import trace
2626
from sentry_sdk.transport import BaseHttpTransport, make_transport
2727
from sentry_sdk.consts import (
28+
SPANDATA,
2829
DEFAULT_MAX_VALUE_LENGTH,
2930
DEFAULT_OPTIONS,
3031
VERSION,
@@ -837,6 +838,13 @@ def _capture_experimental_log(self, current_scope, log):
837838
return
838839
isolation_scope = current_scope.get_isolation_scope()
839840

841+
log["attributes"]["sentry.sdk.name"] = SDK_INFO["name"]
842+
log["attributes"]["sentry.sdk.version"] = SDK_INFO["version"]
843+
844+
server_name = self.options.get("server_name")
845+
if server_name is not None and SPANDATA.SERVER_ADDRESS not in log["attributes"]:
846+
log["attributes"][SPANDATA.SERVER_ADDRESS] = server_name
847+
840848
environment = self.options.get("environment")
841849
if environment is not None and "sentry.environment" not in log["attributes"]:
842850
log["attributes"]["sentry.environment"] = environment

sentry_sdk/integrations/logging.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,9 @@ def _capture_log_from_record(client, record):
363363
# type: (BaseClient, LogRecord) -> None
364364
scope = sentry_sdk.get_current_scope()
365365
otel_severity_number, otel_severity_text = _python_level_to_otel(record.levelno)
366-
attrs = {} # type: dict[str, str | bool | float | int]
366+
attrs = {
367+
"sentry.origin": "auto.logger.log",
368+
} # type: dict[str, str | bool | float | int]
367369
if isinstance(record.msg, str):
368370
attrs["sentry.message.template"] = record.msg
369371
if record.args is not None:

sentry_sdk/transport.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ def _parse_rate_limits(header, now=None):
166166
class BaseHttpTransport(Transport):
167167
"""The base HTTP transport."""
168168

169+
TIMEOUT = 30 # seconds
170+
169171
def __init__(self, options):
170172
# type: (Self, Dict[str, Any]) -> None
171173
from sentry_sdk.consts import VERSION
@@ -558,6 +560,7 @@ def _get_pool_options(self):
558560
options = {
559561
"num_pools": 2 if num_pools is None else int(num_pools),
560562
"cert_reqs": "CERT_REQUIRED",
563+
"timeout": urllib3.Timeout(total=self.TIMEOUT),
561564
}
562565

563566
socket_options = None # type: Optional[List[Tuple[int, int, int | bytes]]]
@@ -673,6 +676,8 @@ def __init__(self, options):
673676
class Http2Transport(BaseHttpTransport): # type: ignore
674677
"""The HTTP2 transport based on httpcore."""
675678

679+
TIMEOUT = 15
680+
676681
if TYPE_CHECKING:
677682
_pool: Union[
678683
httpcore.SOCKSProxy, httpcore.HTTPProxy, httpcore.ConnectionPool
@@ -702,6 +707,14 @@ def _request(
702707
self._auth.get_api_url(endpoint_type),
703708
content=body,
704709
headers=headers, # type: ignore
710+
extensions={
711+
"timeout": {
712+
"pool": self.TIMEOUT,
713+
"connect": self.TIMEOUT,
714+
"write": self.TIMEOUT,
715+
"read": self.TIMEOUT,
716+
}
717+
},
705718
)
706719
return response
707720

tests/test_logs.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sentry_sdk.envelope import Envelope
1212
from sentry_sdk.integrations.logging import LoggingIntegration
1313
from sentry_sdk.types import Log
14+
from sentry_sdk.consts import SPANDATA, VERSION
1415

1516
minimum_python_37 = pytest.mark.skipif(
1617
sys.version_info < (3, 7), reason="Asyncio tests need Python >= 3.7"
@@ -161,7 +162,7 @@ def test_logs_attributes(sentry_init, capture_envelopes):
161162
"""
162163
Passing arbitrary attributes to log messages.
163164
"""
164-
sentry_init(_experiments={"enable_logs": True})
165+
sentry_init(_experiments={"enable_logs": True}, server_name="test-server")
165166
envelopes = capture_envelopes()
166167

167168
attrs = {
@@ -184,6 +185,9 @@ def test_logs_attributes(sentry_init, capture_envelopes):
184185
assert logs[0]["attributes"]["sentry.environment"] == "production"
185186
assert "sentry.release" in logs[0]["attributes"]
186187
assert logs[0]["attributes"]["sentry.message.parameters.my_var"] == "some value"
188+
assert logs[0]["attributes"][SPANDATA.SERVER_ADDRESS] == "test-server"
189+
assert logs[0]["attributes"]["sentry.sdk.name"] == "sentry.python"
190+
assert logs[0]["attributes"]["sentry.sdk.version"] == VERSION
187191

188192

189193
@minimum_python_37
@@ -283,6 +287,7 @@ def test_logger_integration_warning(sentry_init, capture_envelopes):
283287
assert attrs["sentry.environment"] == "production"
284288
assert attrs["sentry.message.parameters.0"] == "1"
285289
assert attrs["sentry.message.parameters.1"] == "2"
290+
assert attrs["sentry.origin"] == "auto.logger.log"
286291
assert logs[0]["severity_number"] == 13
287292
assert logs[0]["severity_text"] == "warn"
288293

tests/test_transport.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
from pytest_localserver.http import WSGIServer
1515
from werkzeug.wrappers import Request, Response
1616

17+
try:
18+
import httpcore
19+
except (ImportError, ModuleNotFoundError):
20+
httpcore = None
21+
22+
try:
23+
import gevent
24+
except ImportError:
25+
gevent = None
26+
1727
import sentry_sdk
1828
from sentry_sdk import (
1929
Client,
@@ -260,6 +270,37 @@ def test_keep_alive_on_by_default(make_client):
260270
assert "socket_options" not in options
261271

262272

273+
def test_default_timeout(make_client):
274+
client = make_client()
275+
276+
options = client.transport._get_pool_options()
277+
assert "timeout" in options
278+
assert options["timeout"].total == client.transport.TIMEOUT
279+
280+
281+
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
282+
def test_default_timeout_http2(make_client):
283+
client = make_client(_experiments={"transport_http2": True})
284+
285+
with mock.patch(
286+
"sentry_sdk.transport.httpcore.ConnectionPool.request",
287+
return_value=httpcore.Response(200),
288+
) as request_mock:
289+
sentry_sdk.get_global_scope().set_client(client)
290+
capture_message("hi")
291+
client.flush()
292+
293+
request_mock.assert_called_once()
294+
assert request_mock.call_args.kwargs["extensions"] == {
295+
"timeout": {
296+
"pool": client.transport.TIMEOUT,
297+
"connect": client.transport.TIMEOUT,
298+
"write": client.transport.TIMEOUT,
299+
"read": client.transport.TIMEOUT,
300+
}
301+
}
302+
303+
263304
@pytest.mark.skipif(not PY38, reason="HTTP2 libraries are only available in py3.8+")
264305
def test_http2_with_https_dsn(make_client):
265306
client = make_client(_experiments={"transport_http2": True})

tox.ini

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# The file (and all resulting CI YAMLs) then need to be regenerated via
1111
# "scripts/generate-test-files.sh".
1212
#
13-
# Last generated: 2025-04-04T13:09:23.106982+00:00
13+
# Last generated: 2025-04-08T10:33:11.499210+00:00
1414

1515
[tox]
1616
requires =
@@ -200,7 +200,7 @@ envlist =
200200
{py3.8,py3.10,py3.11}-strawberry-v0.209.8
201201
{py3.8,py3.11,py3.12}-strawberry-v0.227.7
202202
{py3.8,py3.11,py3.12}-strawberry-v0.245.0
203-
{py3.9,py3.12,py3.13}-strawberry-v0.263.1
203+
{py3.9,py3.12,py3.13}-strawberry-v0.263.2
204204

205205

206206
# ~~~ Network ~~~
@@ -211,9 +211,9 @@ envlist =
211211

212212

213213
# ~~~ Tasks ~~~
214-
{py3.8}-celery-v4.4.7
215-
{py3.8}-celery-v5.0.5
216-
{py3.8,py3.12,py3.13}-celery-v5.5.0
214+
{py3.6,py3.7,py3.8}-celery-v4.4.7
215+
{py3.6,py3.7,py3.8}-celery-v5.0.5
216+
{py3.8,py3.12,py3.13}-celery-v5.5.1
217217

218218
{py3.7}-dramatiq-v1.9.0
219219
{py3.7,py3.8,py3.9}-dramatiq-v1.12.3
@@ -256,7 +256,7 @@ envlist =
256256
{py3.8,py3.10,py3.11}-litestar-v2.0.1
257257
{py3.8,py3.11,py3.12}-litestar-v2.5.5
258258
{py3.8,py3.11,py3.12}-litestar-v2.10.0
259-
{py3.8,py3.12,py3.13}-litestar-v2.15.1
259+
{py3.8,py3.12,py3.13}-litestar-v2.15.2
260260

261261
{py3.7,py3.8,py3.9}-pyramid-v1.10.8
262262
{py3.7,py3.10,py3.11}-pyramid-v2.0.2
@@ -564,7 +564,7 @@ deps =
564564
strawberry-v0.209.8: strawberry-graphql[fastapi,flask]==0.209.8
565565
strawberry-v0.227.7: strawberry-graphql[fastapi,flask]==0.227.7
566566
strawberry-v0.245.0: strawberry-graphql[fastapi,flask]==0.245.0
567-
strawberry-v0.263.1: strawberry-graphql[fastapi,flask]==0.263.1
567+
strawberry-v0.263.2: strawberry-graphql[fastapi,flask]==0.263.2
568568
strawberry: httpx
569569
strawberry-v0.209.8: pydantic<2.11
570570
strawberry-v0.227.7: pydantic<2.11
@@ -585,7 +585,7 @@ deps =
585585
# ~~~ Tasks ~~~
586586
celery-v4.4.7: celery==4.4.7
587587
celery-v5.0.5: celery==5.0.5
588-
celery-v5.5.0: celery==5.5.0
588+
celery-v5.5.1: celery==5.5.1
589589
celery: newrelic
590590
celery: redis
591591

@@ -663,7 +663,7 @@ deps =
663663
litestar-v2.0.1: litestar==2.0.1
664664
litestar-v2.5.5: litestar==2.5.5
665665
litestar-v2.10.0: litestar==2.10.0
666-
litestar-v2.15.1: litestar==2.15.1
666+
litestar-v2.15.2: litestar==2.15.2
667667
litestar: pytest-asyncio
668668
litestar: python-multipart
669669
litestar: requests

0 commit comments

Comments
 (0)