Skip to content

Commit 003dd4d

Browse files
committed
Merge branch 'master' into antonpirker/better-aws-tests
2 parents d12abf5 + 67f0491 commit 003dd4d

File tree

7 files changed

+223
-20
lines changed

7 files changed

+223
-20
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ jobs:
4444
with:
4545
python-version: 3.12
4646

47-
- run: |
47+
- name: Detect unexpected changes to tox.ini or CI
48+
run: |
49+
pip install -e .
50+
pip install -r scripts/populate_tox/requirements.txt
51+
python scripts/populate_tox/populate_tox.py --fail-on-changes
4852
pip install -r scripts/split_tox_gh_actions/requirements.txt
4953
python scripts/split_tox_gh_actions/split_tox_gh_actions.py --fail-on-changes
5054

requirements-docs.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
gevent
22
shibuya
3-
sphinx
3+
sphinx<8.2
44
sphinx-autodoc-typehints[type_comments]>=1.8.0
55
typing-extensions

scripts/populate_tox/populate_tox.py

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
"""
44

55
import functools
6+
import hashlib
67
import os
78
import sys
89
import time
910
from bisect import bisect_left
1011
from collections import defaultdict
12+
from datetime import datetime, timezone
1113
from importlib.metadata import metadata
1214
from packaging.specifiers import SpecifierSet
1315
from packaging.version import Version
1416
from pathlib import Path
17+
from textwrap import dedent
1518
from typing import Optional, Union
1619

1720
# Adding the scripts directory to PATH. This is necessary in order to be able
@@ -106,7 +109,9 @@ def fetch_release(package: str, version: Version) -> dict:
106109
return pypi_data.json()
107110

108111

109-
def _prefilter_releases(integration: str, releases: dict[str, dict]) -> list[Version]:
112+
def _prefilter_releases(
113+
integration: str, releases: dict[str, dict], older_than: Optional[datetime] = None
114+
) -> list[Version]:
110115
"""
111116
Filter `releases`, removing releases that are for sure unsupported.
112117
@@ -135,6 +140,10 @@ def _prefilter_releases(integration: str, releases: dict[str, dict]) -> list[Ver
135140
if meta["yanked"]:
136141
continue
137142

143+
if older_than is not None:
144+
if datetime.fromisoformat(meta["upload_time_iso_8601"]) > older_than:
145+
continue
146+
138147
version = Version(release)
139148

140149
if min_supported and version < min_supported:
@@ -160,19 +169,24 @@ def _prefilter_releases(integration: str, releases: dict[str, dict]) -> list[Ver
160169
return sorted(filtered_releases)
161170

162171

163-
def get_supported_releases(integration: str, pypi_data: dict) -> list[Version]:
172+
def get_supported_releases(
173+
integration: str, pypi_data: dict, older_than: Optional[datetime] = None
174+
) -> list[Version]:
164175
"""
165176
Get a list of releases that are currently supported by the SDK.
166177
167178
This takes into account a handful of parameters (Python support, the lowest
168179
version we've defined for the framework, the date of the release).
180+
181+
If an `older_than` timestamp is provided, no release newer than that will be
182+
considered.
169183
"""
170184
package = pypi_data["info"]["name"]
171185

172186
# Get a consolidated list without taking into account Python support yet
173187
# (because that might require an additional API call for some
174188
# of the releases)
175-
releases = _prefilter_releases(integration, pypi_data["releases"])
189+
releases = _prefilter_releases(integration, pypi_data["releases"], older_than)
176190

177191
# Determine Python support
178192
expected_python_versions = TEST_SUITE_CONFIG[integration].get("python")
@@ -381,7 +395,9 @@ def _render_dependencies(integration: str, releases: list[Version]) -> list[str]
381395
return rendered
382396

383397

384-
def write_tox_file(packages: dict) -> None:
398+
def write_tox_file(
399+
packages: dict, update_timestamp: bool, last_updated: datetime
400+
) -> None:
385401
template = ENV.get_template("tox.jinja")
386402

387403
context = {"groups": {}}
@@ -400,6 +416,11 @@ def write_tox_file(packages: dict) -> None:
400416
}
401417
)
402418

419+
if update_timestamp:
420+
context["updated"] = datetime.now(tz=timezone.utc).isoformat()
421+
else:
422+
context["updated"] = last_updated.isoformat()
423+
403424
rendered = template.render(context)
404425

405426
with open(TOX_FILE, "w") as file:
@@ -453,7 +474,59 @@ def _add_python_versions_to_release(
453474
release.rendered_python_versions = _render_python_versions(release.python_versions)
454475

455476

456-
def main() -> None:
477+
def get_file_hash() -> str:
478+
"""Calculate a hash of the tox.ini file."""
479+
hasher = hashlib.md5()
480+
481+
with open(TOX_FILE, "rb") as f:
482+
buf = f.read()
483+
hasher.update(buf)
484+
485+
return hasher.hexdigest()
486+
487+
488+
def get_last_updated() -> Optional[datetime]:
489+
timestamp = None
490+
491+
with open(TOX_FILE, "r") as f:
492+
for line in f:
493+
if line.startswith("# Last generated:"):
494+
timestamp = datetime.fromisoformat(line.strip().split()[-1])
495+
break
496+
497+
if timestamp is None:
498+
print(
499+
"Failed to find out when tox.ini was last generated; the timestamp seems to be missing from the file."
500+
)
501+
502+
return timestamp
503+
504+
505+
def main(fail_on_changes: bool = False) -> None:
506+
"""
507+
Generate tox.ini from the tox.jinja template.
508+
509+
The script has two modes of operation:
510+
- fail on changes mode (if `fail_on_changes` is True)
511+
- normal mode (if `fail_on_changes` is False)
512+
513+
Fail on changes mode is run on every PR to make sure that `tox.ini`,
514+
`tox.jinja` and this script don't go out of sync because of manual changes
515+
in one place but not the other.
516+
517+
Normal mode is meant to be run as a cron job, regenerating tox.ini and
518+
proposing the changes via a PR.
519+
"""
520+
print(f"Running in {'fail_on_changes' if fail_on_changes else 'normal'} mode.")
521+
last_updated = get_last_updated()
522+
if fail_on_changes:
523+
# We need to make the script ignore any new releases after the `last_updated`
524+
# timestamp so that we don't fail CI on a PR just because a new package
525+
# version was released, leading to unrelated changes in tox.ini.
526+
print(
527+
f"Since we're in fail_on_changes mode, we're only considering releases before the last tox.ini update at {last_updated.isoformat()}."
528+
)
529+
457530
global MIN_PYTHON_VERSION, MAX_PYTHON_VERSION
458531
sdk_python_versions = _parse_python_versions_from_classifiers(
459532
metadata("sentry-sdk").get_all("Classifier")
@@ -480,7 +553,9 @@ def main() -> None:
480553
pypi_data = fetch_package(package)
481554

482555
# Get the list of all supported releases
483-
releases = get_supported_releases(integration, pypi_data)
556+
# If in check mode, ignore releases newer than `last_updated`
557+
older_than = last_updated if fail_on_changes else None
558+
releases = get_supported_releases(integration, pypi_data, older_than)
484559
if not releases:
485560
print(" Found no supported releases.")
486561
continue
@@ -510,8 +585,44 @@ def main() -> None:
510585
}
511586
)
512587

513-
write_tox_file(packages)
588+
if fail_on_changes:
589+
old_file_hash = get_file_hash()
590+
591+
write_tox_file(
592+
packages, update_timestamp=not fail_on_changes, last_updated=last_updated
593+
)
594+
595+
if fail_on_changes:
596+
new_file_hash = get_file_hash()
597+
if old_file_hash != new_file_hash:
598+
raise RuntimeError(
599+
dedent(
600+
"""
601+
Detected that `tox.ini` is out of sync with
602+
`scripts/populate_tox/tox.jinja` and/or
603+
`scripts/populate_tox/populate_tox.py`. This might either mean
604+
that `tox.ini` was changed manually, or the `tox.jinja`
605+
template and/or the `populate_tox.py` script were changed without
606+
regenerating `tox.ini`.
607+
608+
Please don't make manual changes to `tox.ini`. Instead, make the
609+
changes to the `tox.jinja` template and/or the `populate_tox.py`
610+
script (as applicable) and regenerate the `tox.ini` file with:
611+
612+
python -m venv toxgen.env
613+
. toxgen.env/bin/activate
614+
pip install -r scripts/populate_tox/requirements.txt
615+
python scripts/populate_tox/populate_tox.py
616+
"""
617+
)
618+
)
619+
print("Done checking tox.ini. Looking good!")
620+
else:
621+
print(
622+
"Done generating tox.ini. Make sure to also update the CI YAML files to reflect the new test targets."
623+
)
514624

515625

516626
if __name__ == "__main__":
517-
main()
627+
fail_on_changes = len(sys.argv) == 2 and sys.argv[1] == "--fail-on-changes"
628+
main(fail_on_changes)

scripts/populate_tox/tox.jinja

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
# or in the script (if you want to change the auto-generated part).
1010
# The file (and all resulting CI YAMLs) then need to be regenerated via
1111
# "scripts/generate-test-files.sh".
12+
#
13+
# Last generated: {{ updated }}
1214

1315
[tox]
1416
requires =

sentry_sdk/profiler/continuous_profiler.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ def try_profile_lifecycle_trace_start():
145145

146146
def start_profiler():
147147
# type: () -> None
148+
149+
# TODO: deprecate this as it'll be replaced by `start_profile_session`
150+
start_profile_session()
151+
152+
153+
def start_profile_session():
154+
# type: () -> None
148155
if _scheduler is None:
149156
return
150157

@@ -153,6 +160,13 @@ def start_profiler():
153160

154161
def stop_profiler():
155162
# type: () -> None
163+
164+
# TODO: deprecate this as it'll be replaced by `stop_profile_session`
165+
stop_profile_session()
166+
167+
168+
def stop_profile_session():
169+
# type: () -> None
156170
if _scheduler is None:
157171
return
158172

0 commit comments

Comments
 (0)