Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: getsentry/sentry-python
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 2.23.1
Choose a base ref
...
head repository: getsentry/sentry-python
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2.24.0
Choose a head ref
  • 13 commits
  • 20 files changed
  • 8 contributors

Commits on Mar 17, 2025

  1. Merge branch 'release/2.23.1'

    getsentry-bot committed Mar 17, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5dcda1d View commit details

Commits on Mar 18, 2025

  1. Support Starlette/FastAPI app.host (#4157)

    In Starlette/FastAPI you're able to create subapps. When using
    `transaction_style="url"` in our integration, this would throw an
    exception because we try to access `route.path` to determine the
    transaction name, but `Host` routes have no `path` attribute.
    
    Closes #2631
    sentrivana authored Mar 18, 2025
    Copy the full SHA
    e85715a View commit details
  2. feat(tests): Update tox.ini (#4146)

    Regular `tox.ini` update
    sentrivana authored Mar 18, 2025
    Copy the full SHA
    bc54a1d View commit details

Commits on Mar 19, 2025

  1. Handle loguru msg levels that are not supported by Sentry (#4147)

    Loguru has two message levels `TRACE` and `SUCCESS` that are not
    available in Sentry breadcrumbs. This PR maps `TRACE` to `debug` and
    `SUCCESS` to `info` in Sentry so those breadcrumbs do not show a
    confusing error message in the Sentry UI.
    
    Fixes #2759
    antonpirker authored Mar 19, 2025
    Copy the full SHA
    11abdd2 View commit details
  2. style(integrations): Fix captured typo (#4161)

    Small typo fix
    pimuzzo authored Mar 19, 2025
    Copy the full SHA
    65132ba View commit details
  3. Reset DedupeIntegration's last-seen if before_send dropped the …

    …event (#4142)
    
    Imagine an app throws an exception twice, from different places. The
    first exception is dropped in the user's `before_send`. The second
    exception is not. Should the second exception appear in Sentry?
    
    The current state is that it won't, since `DedupeIntegration` will take
    the first, dropped exception into account. When encountering the second
    exception, it'll consider it a duplicate and will drop it, even though
    the first exception never made it to Sentry.
    
    In this PR, we reset `DedupeIntegration`'s `last-seen` if an event has
    been dropped by `before_send`, ensuring that the next exception will be
    reported.
    
    Closes #371
    
    ---------
    
    Co-authored-by: Anton Pirker <anton.pirker@sentry.io>
    sentrivana and antonpirker authored Mar 19, 2025
    Copy the full SHA
    0d3bc3d View commit details
  4. feat(profiling): reverse profile_session start/stop methods deprecati…

    …on (#4162)
    
    Revert back to using `start_profiler` and `stop_profiler` function names
    and deprecate the `*_session` ones instead.
    
    Prior PR that introduced the change we're undoing:
    #4056
    viglia authored Mar 19, 2025
    Copy the full SHA
    f6db981 View commit details

Commits on Mar 20, 2025

  1. chore(profiler): Add deprecation warning for session functions (#4171)

    We're deprecating the short-lived `start_profile_session` and
    `stop_profile_session` functions in favor of `start_profiler` and
    `stop_profiler`, respectively.
    
    The functions will be dropped in 3.x, see
    #4170
    sentrivana authored Mar 20, 2025
    Copy the full SHA
    eb189ef View commit details
  2. Fixed flaky test (#4165)

    The URL www.squirrelchasers.com is actually existing, so we should not
    access it in our tests. Hope this make the test more stable.
    antonpirker authored Mar 20, 2025
    Copy the full SHA
    f76528f View commit details
  3. Update scripts sources (#4166)

    # PR Summary
    Small PR - Commit d4f4130 moved
    scripts. This PR adjusts sources to changes.
    
    Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
    emmanuel-ferdman authored Mar 20, 2025
    Copy the full SHA
    2579cb2 View commit details
  4. Fix memory leak by not piling up breadcrumbs forever in Spark workers. (

    #4167)
    
    We now clear all existing breadcrumbs when a job is started. If an error
    happens in a job, only breadcrumbs created in this job will be shown.
    
    
    Fixes #1245.
    antonpirker authored Mar 20, 2025
    Copy the full SHA
    5715734 View commit details
  5. fix(tracing): Fix InvalidOperation (#4179)

    `InvalidOperation` can occur when using tracing if the `Decimal` class's
    global context has been modified to set the precision below 6. This
    change fixes this bug by setting a custom context for our `quantize`
    call.
    
    Fixes #4177
    szokeasaurusrex authored Mar 20, 2025
    Copy the full SHA
    12b3ca3 View commit details

Commits on Mar 21, 2025

  1. release: 2.24.0

    getsentry-bot committed Mar 21, 2025
    Copy the full SHA
    a3356d7 View commit details
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 2.24.0

### Various fixes & improvements

- fix(tracing): Fix `InvalidOperation` (#4179) by @szokeasaurusrex
- Fix memory leak by not piling up breadcrumbs forever in Spark workers. (#4167) by @antonpirker
- Update scripts sources (#4166) by @emmanuel-ferdman
- Fixed flaky test (#4165) by @antonpirker
- chore(profiler): Add deprecation warning for session functions (#4171) by @sentrivana
- feat(profiling): reverse profile_session start/stop methods deprecation (#4162) by @viglia
- Reset `DedupeIntegration`'s `last-seen` if `before_send` dropped the event (#4142) by @sentrivana
- style(integrations): Fix captured typo (#4161) by @pimuzzo
- Handle loguru msg levels that are not supported by Sentry (#4147) by @antonpirker
- feat(tests): Update tox.ini (#4146) by @sentrivana
- Support Starlette/FastAPI `app.host` (#4157) by @sentrivana

## 2.23.1

### Various fixes & improvements
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -182,14 +182,14 @@ You need to have an AWS account and AWS CLI installed and setup.

We put together two helper functions that can help you with development:

- `./scripts/aws-deploy-local-layer.sh`
- `./scripts/aws/aws-deploy-local-layer.sh`

This script [scripts/aws-deploy-local-layer.sh](scripts/aws-deploy-local-layer.sh) will take the code you have checked out locally, create a Lambda layer out of it and deploy it to the `eu-central-1` region of your configured AWS account using `aws` CLI.
This script [scripts/aws/aws-deploy-local-layer.sh](scripts/aws/aws-deploy-local-layer.sh) will take the code you have checked out locally, create a Lambda layer out of it and deploy it to the `eu-central-1` region of your configured AWS account using `aws` CLI.

The Lambda layer will have the name `SentryPythonServerlessSDK-local-dev`

- `./scripts/aws-attach-layer-to-lambda-function.sh`
- `./scripts/aws/aws-attach-layer-to-lambda-function.sh`

You can use this script [scripts/aws-attach-layer-to-lambda-function.sh](scripts/aws-attach-layer-to-lambda-function.sh) to attach the Lambda layer you just deployed (using the first script) onto one of your existing Lambda functions. You will have to give the name of the Lambda function to attach onto as an argument. (See the script for details.)
You can use this script [scripts/aws/aws-attach-layer-to-lambda-function.sh](scripts/aws/aws-attach-layer-to-lambda-function.sh) to attach the Lambda layer you just deployed (using the first script) onto one of your existing Lambda functions. You will have to give the name of the Lambda function to attach onto as an argument. (See the script for details.)

With these two helper scripts it should be easy to rapidly iterate your development on the Lambda layer.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@
copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year)
author = "Sentry Team and Contributors"

release = "2.23.1"
release = "2.24.0"
version = ".".join(release.split(".")[:2]) # The short X.Y version.


9 changes: 9 additions & 0 deletions sentry_sdk/client.py
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
ClientConstructor,
)
from sentry_sdk.integrations import _DEFAULT_INTEGRATIONS, setup_integrations
from sentry_sdk.integrations.dedupe import DedupeIntegration
from sentry_sdk.sessions import SessionFlusher
from sentry_sdk.envelope import Envelope
from sentry_sdk.profiler.continuous_profiler import setup_continuous_profiler
@@ -606,6 +607,14 @@ def _prepare_event(
self.transport.record_lost_event(
"before_send", data_category="error"
)

# If this is an exception, reset the DedupeIntegration. It still
# remembers the dropped exception as the last exception, meaning
# that if the same exception happens again and is not dropped
# in before_send, it'd get dropped by DedupeIntegration.
if event.get("exception"):
DedupeIntegration.reset_last_seen()

event = new_event

before_send_transaction = self.options["before_send_transaction"]
2 changes: 1 addition & 1 deletion sentry_sdk/consts.py
Original file line number Diff line number Diff line change
@@ -965,4 +965,4 @@ def _get_default_options():
del _get_default_options


VERSION = "2.23.1"
VERSION = "2.24.0"
9 changes: 9 additions & 0 deletions sentry_sdk/integrations/dedupe.py
Original file line number Diff line number Diff line change
@@ -40,3 +40,12 @@ def processor(event, hint):
return None
integration._last_seen.set(exc)
return event

@staticmethod
def reset_last_seen():
# type: () -> None
integration = sentry_sdk.get_client().get_integration(DedupeIntegration)
if integration is None:
return

integration._last_seen.set(None)
4 changes: 2 additions & 2 deletions sentry_sdk/integrations/logging.py
Original file line number Diff line number Diff line change
@@ -232,10 +232,10 @@ def _emit(self, record):
event["logger"] = record.name

# Log records from `warnings` module as separate issues
record_caputured_from_warnings_module = (
record_captured_from_warnings_module = (
record.name == "py.warnings" and record.msg == "%s"
)
if record_caputured_from_warnings_module:
if record_captured_from_warnings_module:
# use the actual message and not "%s" as the message
# this prevents grouping all warnings under one "%s" issue
msg = record.args[0] # type: ignore
36 changes: 33 additions & 3 deletions sentry_sdk/integrations/loguru.py
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@

if TYPE_CHECKING:
from logging import LogRecord
from typing import Optional, Tuple
from typing import Optional, Tuple, Any

try:
import loguru
@@ -31,6 +31,16 @@ class LoggingLevels(enum.IntEnum):
CRITICAL = 50


SENTRY_LEVEL_FROM_LOGURU_LEVEL = {
"TRACE": "DEBUG",
"DEBUG": "DEBUG",
"INFO": "INFO",
"SUCCESS": "INFO",
"WARNING": "WARNING",
"ERROR": "ERROR",
"CRITICAL": "CRITICAL",
}

DEFAULT_LEVEL = LoggingLevels.INFO.value
DEFAULT_EVENT_LEVEL = LoggingLevels.ERROR.value
# We need to save the handlers to be able to remove them later
@@ -87,14 +97,34 @@ class _LoguruBaseHandler(_BaseHandler):
def _logging_to_event_level(self, record):
# type: (LogRecord) -> str
try:
return LoggingLevels(record.levelno).name.lower()
except ValueError:
return SENTRY_LEVEL_FROM_LOGURU_LEVEL[
LoggingLevels(record.levelno).name
].lower()
except (ValueError, KeyError):
return record.levelname.lower() if record.levelname else ""


class LoguruEventHandler(_LoguruBaseHandler, EventHandler):
"""Modified version of :class:`sentry_sdk.integrations.logging.EventHandler` to use loguru's level names."""

def __init__(self, *args, **kwargs):
# type: (*Any, **Any) -> None
if kwargs.get("level"):
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
kwargs.get("level", ""), DEFAULT_LEVEL
)

super().__init__(*args, **kwargs)


class LoguruBreadcrumbHandler(_LoguruBaseHandler, BreadcrumbHandler):
"""Modified version of :class:`sentry_sdk.integrations.logging.BreadcrumbHandler` to use loguru's level names."""

def __init__(self, *args, **kwargs):
# type: (*Any, **Any) -> None
if kwargs.get("level"):
kwargs["level"] = SENTRY_LEVEL_FROM_LOGURU_LEVEL.get(
kwargs.get("level", ""), DEFAULT_LEVEL
)

super().__init__(*args, **kwargs)
12 changes: 9 additions & 3 deletions sentry_sdk/integrations/spark/spark_driver.py
Original file line number Diff line number Diff line change
@@ -31,9 +31,13 @@ def _set_app_properties():

spark_context = SparkContext._active_spark_context
if spark_context:
spark_context.setLocalProperty("sentry_app_name", spark_context.appName)
spark_context.setLocalProperty(
"sentry_application_id", spark_context.applicationId
"sentry_app_name",
spark_context.appName,
)
spark_context.setLocalProperty(
"sentry_application_id",
spark_context.applicationId,
)


@@ -231,12 +235,14 @@ def _add_breadcrumb(
data=None, # type: Optional[dict[str, Any]]
):
# type: (...) -> None
sentry_sdk.get_global_scope().add_breadcrumb(
sentry_sdk.get_isolation_scope().add_breadcrumb(
level=level, message=message, data=data
)

def onJobStart(self, jobStart): # noqa: N802,N803
# type: (Any) -> None
sentry_sdk.get_isolation_scope().clear_breadcrumbs()

message = "Job {} Started".format(jobStart.jobId())
self._add_breadcrumb(level="info", message=message)
_set_app_properties()
6 changes: 5 additions & 1 deletion sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
@@ -693,7 +693,11 @@ def _transaction_name_from_router(scope):
for route in router.routes:
match = route.matches(scope)
if match[0] == Match.FULL:
return route.path
try:
return route.path
except AttributeError:
# routes added via app.host() won't have a path attribute
return scope.get("path")

return None

8 changes: 4 additions & 4 deletions sentry_sdk/profiler/__init__.py
Original file line number Diff line number Diff line change
@@ -25,10 +25,10 @@
)

__all__ = [
"start_profile_session",
"start_profiler", # TODO: Deprecate this in favor of `start_profile_session`
"stop_profile_session",
"stop_profiler", # TODO: Deprecate this in favor of `stop_profile_session`
"start_profile_session", # TODO: Deprecate this in favor of `start_profiler`
"start_profiler",
"stop_profile_session", # TODO: Deprecate this in favor of `stop_profiler`
"stop_profiler",
# DEPRECATED: The following was re-exported for backwards compatibility. It
# will be removed from sentry_sdk.profiler in a future release.
"MAX_PROFILE_DURATION_NS",
29 changes: 19 additions & 10 deletions sentry_sdk/profiler/continuous_profiler.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
import threading
import time
import uuid
import warnings
from collections import deque
from datetime import datetime, timezone

@@ -145,32 +146,40 @@ def try_profile_lifecycle_trace_start():

def start_profiler():
# type: () -> None
if _scheduler is None:
return

# TODO: deprecate this as it'll be replaced by `start_profile_session`
start_profile_session()
_scheduler.manual_start()


def start_profile_session():
# type: () -> None
if _scheduler is None:
return

_scheduler.manual_start()
warnings.warn(
"The `start_profile_session` function is deprecated. Please use `start_profile` instead.",
DeprecationWarning,
stacklevel=2,
)
start_profiler()


def stop_profiler():
# type: () -> None
if _scheduler is None:
return

# TODO: deprecate this as it'll be replaced by `stop_profile_session`
stop_profile_session()
_scheduler.manual_stop()


def stop_profile_session():
# type: () -> None
if _scheduler is None:
return

_scheduler.manual_stop()
warnings.warn(
"The `stop_profile_session` function is deprecated. Please use `stop_profile` instead.",
DeprecationWarning,
stacklevel=2,
)
stop_profiler()


def teardown_continuous_profiler():
8 changes: 6 additions & 2 deletions sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
import sys
from collections.abc import Mapping
from datetime import timedelta
from decimal import ROUND_DOWN, Decimal
from decimal import ROUND_DOWN, Context, Decimal
from functools import wraps
from random import Random
from urllib.parse import quote, unquote
@@ -871,7 +871,11 @@ def _generate_sample_rand(
sample_rand = rng.uniform(lower, upper)

# Round down to exactly six decimal-digit precision.
return Decimal(sample_rand).quantize(Decimal("0.000001"), rounding=ROUND_DOWN)
# Setting the context is needed to avoid an InvalidOperation exception
# in case the user has changed the default precision.
return Decimal(sample_rand).quantize(
Decimal("0.000001"), rounding=ROUND_DOWN, context=Context(prec=6)
)


def _sample_rand_range(parent_sampled, sample_rate):
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@ def get_file_text(file_name):

setup(
name="sentry-sdk",
version="2.23.1",
version="2.24.0",
author="Sentry Team and Contributors",
author_email="hello@sentry.io",
url="https://github.com/getsentry/sentry-python",
35 changes: 35 additions & 0 deletions tests/integrations/fastapi/test_fastapi.py
Original file line number Diff line number Diff line change
@@ -682,3 +682,38 @@ async def _error():
client.get("/error")

assert len(events) == int(expected_error)


@pytest.mark.parametrize("transaction_style", ["endpoint", "url"])
def test_app_host(sentry_init, capture_events, transaction_style):
sentry_init(
traces_sample_rate=1.0,
integrations=[
StarletteIntegration(transaction_style=transaction_style),
FastApiIntegration(transaction_style=transaction_style),
],
)

app = FastAPI()
subapp = FastAPI()

@subapp.get("/subapp")
async def subapp_route():
return {"message": "Hello world!"}

app.host("subapp", subapp)

events = capture_events()

client = TestClient(app)
client.get("/subapp", headers={"Host": "subapp"})

assert len(events) == 1

(event,) = events
assert "transaction" in event

if transaction_style == "url":
assert event["transaction"] == "/subapp"
else:
assert event["transaction"].endswith("subapp_route")
23 changes: 12 additions & 11 deletions tests/integrations/loguru/test_loguru.py
Original file line number Diff line number Diff line change
@@ -8,18 +8,18 @@


@pytest.mark.parametrize(
"level,created_event",
"level,created_event,expected_sentry_level",
[
# None - no breadcrumb
# False - no event
# True - event created
(LoggingLevels.TRACE, None),
(LoggingLevels.DEBUG, None),
(LoggingLevels.INFO, False),
(LoggingLevels.SUCCESS, False),
(LoggingLevels.WARNING, False),
(LoggingLevels.ERROR, True),
(LoggingLevels.CRITICAL, True),
(LoggingLevels.TRACE, None, "debug"),
(LoggingLevels.DEBUG, None, "debug"),
(LoggingLevels.INFO, False, "info"),
(LoggingLevels.SUCCESS, False, "info"),
(LoggingLevels.WARNING, False, "warning"),
(LoggingLevels.ERROR, True, "error"),
(LoggingLevels.CRITICAL, True, "critical"),
],
)
@pytest.mark.parametrize("disable_breadcrumbs", [True, False])
@@ -29,6 +29,7 @@ def test_just_log(
capture_events,
level,
created_event,
expected_sentry_level,
disable_breadcrumbs,
disable_events,
):
@@ -48,7 +49,7 @@ def test_just_log(
formatted_message = (
" | "
+ "{:9}".format(level.name.upper())
+ "| tests.integrations.loguru.test_loguru:test_just_log:46 - test"
+ "| tests.integrations.loguru.test_loguru:test_just_log:47 - test"
)

if not created_event:
@@ -59,7 +60,7 @@ def test_just_log(
not disable_breadcrumbs and created_event is not None
): # not None == not TRACE or DEBUG level
(breadcrumb,) = breadcrumbs
assert breadcrumb["level"] == level.name.lower()
assert breadcrumb["level"] == expected_sentry_level
assert breadcrumb["category"] == "tests.integrations.loguru.test_loguru"
assert breadcrumb["message"][23:] == formatted_message
else:
@@ -72,7 +73,7 @@ def test_just_log(
return

(event,) = events
assert event["level"] == (level.name.lower())
assert event["level"] == expected_sentry_level
assert event["logger"] == "tests.integrations.loguru.test_loguru"
assert event["logentry"]["message"][23:] == formatted_message

25 changes: 8 additions & 17 deletions tests/integrations/stdlib/test_httplib.py
Original file line number Diff line number Diff line change
@@ -398,25 +398,16 @@ def test_http_timeout(monkeypatch, sentry_init, capture_envelopes):

envelopes = capture_envelopes()

with start_transaction(op="op", name="name"):
try:
conn = HTTPSConnection("www.squirrelchasers.com")
conn.request("GET", "/top-chasers")
with pytest.raises(TimeoutError):
with start_transaction(op="op", name="name"):
conn = HTTPSConnection("www.example.com")
conn.request("GET", "/bla")
conn.getresponse()
except Exception:
pass

items = [
item
for envelope in envelopes
for item in envelope.items
if item.type == "transaction"
]
assert len(items) == 1

transaction = items[0].payload.json

(transaction_envelope,) = envelopes
transaction = transaction_envelope.get_transaction_event()
assert len(transaction["spans"]) == 1

span = transaction["spans"][0]
assert span["op"] == "http.client"
assert span["description"] == "GET https://www.squirrelchasers.com/top-chasers"
assert span["description"] == "GET https://www.example.com/bla"
31 changes: 31 additions & 0 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
@@ -710,6 +710,37 @@ def test_dedupe_event_processor_drop_records_client_report(
assert lost_event_call == ("event_processor", "error", None, 1)


def test_dedupe_doesnt_take_into_account_dropped_exception(sentry_init, capture_events):
# Two exceptions happen one after another. The first one is dropped in the
# user's before_send. The second one isn't.
# Originally, DedupeIntegration would drop the second exception. This test
# is making sure that that is no longer the case -- i.e., DedupeIntegration
# doesn't consider exceptions dropped in before_send.
count = 0

def before_send(event, hint):
nonlocal count
count += 1
if count == 1:
return None
return event

sentry_init(before_send=before_send)
events = capture_events()

exc = ValueError("aha!")
for _ in range(2):
# The first ValueError will be dropped by before_send. The second
# ValueError will be accepted by before_send, and should be sent to
# Sentry.
try:
raise exc
except Exception:
capture_exception()

assert len(events) == 1


def test_event_processor_drop_records_client_report(
sentry_init, capture_events, capture_record_lost_event_calls
):
26 changes: 26 additions & 0 deletions tests/tracing/test_sample_rand.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import decimal
from unittest import mock

import pytest
@@ -53,3 +54,28 @@ def test_transaction_uses_incoming_sample_rand(
# Transaction event captured if sample_rand < sample_rate, indicating that
# sample_rand is used to make the sampling decision.
assert len(events) == int(sample_rand < sample_rate)


def test_decimal_context(sentry_init, capture_events):
"""
Ensure that having a decimal context with a precision below 6
does not cause an InvalidOperation exception.
"""
sentry_init(traces_sample_rate=1.0)
events = capture_events()

old_prec = decimal.getcontext().prec
decimal.getcontext().prec = 2

try:
with mock.patch(
"sentry_sdk.tracing_utils.Random.uniform", return_value=0.123456789
):
with sentry_sdk.start_transaction() as transaction:
assert (
transaction.get_baggage().sentry_items["sample_rand"] == "0.123456"
)
finally:
decimal.getcontext().prec = old_prec

assert len(events) == 1
22 changes: 11 additions & 11 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
# The file (and all resulting CI YAMLs) then need to be regenerated via
# "scripts/generate-test-files.sh".
#
# Last generated: 2025-03-10T11:46:25.287445+00:00
# Last generated: 2025-03-18T10:29:17.585636+00:00

[tox]
requires =
@@ -187,12 +187,13 @@ envlist =
{py3.6,py3.7}-sqlalchemy-v1.3.9
{py3.6,py3.11,py3.12}-sqlalchemy-v1.4.54
{py3.7,py3.10,py3.11}-sqlalchemy-v2.0.9
{py3.7,py3.12,py3.13}-sqlalchemy-v2.0.38
{py3.7,py3.12,py3.13}-sqlalchemy-v2.0.39


# ~~~ Flags ~~~
{py3.8,py3.12,py3.13}-launchdarkly-v9.8.1
{py3.8,py3.12,py3.13}-launchdarkly-v9.9.0
{py3.8,py3.12,py3.13}-launchdarkly-v9.10.0

{py3.8,py3.12,py3.13}-openfeature-v0.7.5
{py3.9,py3.12,py3.13}-openfeature-v0.8.0
@@ -222,15 +223,14 @@ envlist =
{py3.8,py3.10,py3.11}-strawberry-v0.209.8
{py3.8,py3.11,py3.12}-strawberry-v0.227.7
{py3.8,py3.11,py3.12}-strawberry-v0.245.0
{py3.9,py3.12,py3.13}-strawberry-v0.262.1
{py3.9,py3.12,py3.13}-strawberry-v0.262.5


# ~~~ Network ~~~
{py3.7,py3.8}-grpc-v1.32.0
{py3.7,py3.9,py3.10}-grpc-v1.44.0
{py3.7,py3.10,py3.11}-grpc-v1.58.3
{py3.8,py3.12,py3.13}-grpc-v1.70.0
{py3.9,py3.12,py3.13}-grpc-v1.71.0rc2
{py3.9,py3.12,py3.13}-grpc-v1.71.0


# ~~~ Tasks ~~~
@@ -294,7 +294,7 @@ envlist =
{py3.6,py3.7,py3.8}-trytond-v5.8.16
{py3.8,py3.10,py3.11}-trytond-v6.8.17
{py3.8,py3.11,py3.12}-trytond-v7.0.9
{py3.8,py3.11,py3.12}-trytond-v7.4.7
{py3.8,py3.11,py3.12}-trytond-v7.4.8

{py3.7,py3.12,py3.13}-typer-v0.15.2

@@ -578,12 +578,13 @@ deps =
sqlalchemy-v1.3.9: sqlalchemy==1.3.9
sqlalchemy-v1.4.54: sqlalchemy==1.4.54
sqlalchemy-v2.0.9: sqlalchemy==2.0.9
sqlalchemy-v2.0.38: sqlalchemy==2.0.38
sqlalchemy-v2.0.39: sqlalchemy==2.0.39


# ~~~ Flags ~~~
launchdarkly-v9.8.1: launchdarkly-server-sdk==9.8.1
launchdarkly-v9.9.0: launchdarkly-server-sdk==9.9.0
launchdarkly-v9.10.0: launchdarkly-server-sdk==9.10.0

openfeature-v0.7.5: openfeature-sdk==0.7.5
openfeature-v0.8.0: openfeature-sdk==0.8.0
@@ -622,16 +623,15 @@ deps =
strawberry-v0.209.8: strawberry-graphql[fastapi,flask]==0.209.8
strawberry-v0.227.7: strawberry-graphql[fastapi,flask]==0.227.7
strawberry-v0.245.0: strawberry-graphql[fastapi,flask]==0.245.0
strawberry-v0.262.1: strawberry-graphql[fastapi,flask]==0.262.1
strawberry-v0.262.5: strawberry-graphql[fastapi,flask]==0.262.5
strawberry: httpx


# ~~~ Network ~~~
grpc-v1.32.0: grpcio==1.32.0
grpc-v1.44.0: grpcio==1.44.0
grpc-v1.58.3: grpcio==1.58.3
grpc-v1.70.0: grpcio==1.70.0
grpc-v1.71.0rc2: grpcio==1.71.0rc2
grpc-v1.71.0: grpcio==1.71.0
grpc: protobuf
grpc: mypy-protobuf
grpc: types-protobuf
@@ -729,7 +729,7 @@ deps =
trytond-v5.8.16: trytond==5.8.16
trytond-v6.8.17: trytond==6.8.17
trytond-v7.0.9: trytond==7.0.9
trytond-v7.4.7: trytond==7.4.7
trytond-v7.4.8: trytond==7.4.8
trytond: werkzeug
trytond-v4.6.9: werkzeug<1.0
trytond-v4.8.18: werkzeug<1.0