Update to v2.15.0 (fedora#2310848)
This commit is contained in:
parent
30fb39586a
commit
2f8f40f1d8
6 changed files with 117 additions and 342 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -28,3 +28,4 @@
|
|||
/sentry-python-2.1.1.tar.gz
|
||||
/sentry-python-2.7.1.tar.gz
|
||||
/sentry-python-2.13.0.tar.gz
|
||||
/sentry-python-2.15.0.tar.gz
|
||||
|
|
|
@ -1,238 +0,0 @@
|
|||
From da164dad9eea765f2156cc44ef9a86a848aff4d9 Mon Sep 17 00:00:00 2001
|
||||
From: Roman Inflianskas <rominf@pm.me>
|
||||
Date: Thu, 18 Jul 2024 18:54:14 +0300
|
||||
Subject: [PATCH 1/2] test(tracing): Test `add_query_source` with modules
|
||||
outside of project root
|
||||
|
||||
When packages added in `in_app_include` are installed to a location
|
||||
outside of the project root directory, span from those packages are not
|
||||
extended with OTel compatible source code information. Cases include
|
||||
running Python from virtualenv created outside of the project root
|
||||
directory or Python packages installed into the system using package
|
||||
managers. This results in an inconsistency: spans from the same project
|
||||
are be different, depending on the deployment method.
|
||||
|
||||
The change extends `test_query_source_with_in_app_include` to test the
|
||||
simulation of Django installed outside of the project root.
|
||||
|
||||
The steps to manually reproduce the issue are as follows
|
||||
(case: a virtual environment created outside of the project root):
|
||||
```bash
|
||||
docker run --replace --rm --detach \
|
||||
--name sentry-postgres \
|
||||
--env POSTGRES_USER=sentry \
|
||||
--env POSTGRES_PASSWORD=sentry \
|
||||
--publish 5432:5432 \
|
||||
postgres
|
||||
distrobox create \
|
||||
--image ubuntu:24.04 \
|
||||
--name sentry-test-in_app_include-venv
|
||||
distrobox enter sentry-test-in_app_include-venv
|
||||
python3 -m venv /tmp/.venv-test-in_app_include
|
||||
source /tmp/.venv-test-in_app_include/bin/activate
|
||||
pip install \
|
||||
-r requirements-devenv.txt \
|
||||
pytest-django \
|
||||
psycopg2-binary \
|
||||
-e .[django]
|
||||
export SENTRY_PYTHON_TEST_POSTGRES_USER=sentry
|
||||
export SENTRY_PYTHON_TEST_POSTGRES_PASSWORD=sentry
|
||||
pytest tests/integrations/django/test_db_query_data.py \
|
||||
-k test_query_source_with_in_app_include # FAIL
|
||||
```
|
||||
|
||||
The steps to manually reproduce the issue are as follows
|
||||
(case: Django is installed through system packages):
|
||||
```bash
|
||||
docker run --replace --rm --detach \
|
||||
--name sentry-postgres \
|
||||
--env POSTGRES_USER=sentry \
|
||||
--env POSTGRES_PASSWORD=sentry \
|
||||
--publish 5432:5432 \
|
||||
postgres
|
||||
distrobox create \
|
||||
--image ubuntu:24.04 \
|
||||
--name sentry-test-in_app_include-os
|
||||
distrobox enter sentry-test-in_app_include-os
|
||||
sudo apt install \
|
||||
python3-django python3-pytest python3-pytest-cov \
|
||||
python3-pytest-django python3-jsonschema python3-urllib3 \
|
||||
python3-certifi python3-werkzeug python3-psycopg2
|
||||
export SENTRY_PYTHON_TEST_POSTGRES_USER=sentry
|
||||
export SENTRY_PYTHON_TEST_POSTGRES_PASSWORD=sentry
|
||||
pytest tests/integrations/django/test_db_query_data.py \
|
||||
-k test_query_source_with_in_app_include # FAIL
|
||||
```
|
||||
---
|
||||
sentry_sdk/tracing_utils.py | 21 ++++----
|
||||
sentry_sdk/utils.py | 5 +-
|
||||
.../integrations/django/test_db_query_data.py | 54 +++++++++++++++++--
|
||||
3 files changed, 66 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py
|
||||
index ba20dc84..47e14ecd 100644
|
||||
--- a/sentry_sdk/tracing_utils.py
|
||||
+++ b/sentry_sdk/tracing_utils.py
|
||||
@@ -170,6 +170,14 @@ def maybe_create_breadcrumbs_from_span(scope, span):
|
||||
)
|
||||
|
||||
|
||||
+def _get_frame_module_abs_path(frame):
|
||||
+ # type: (FrameType) -> Optional[str]
|
||||
+ try:
|
||||
+ return frame.f_code.co_filename
|
||||
+ except Exception:
|
||||
+ return None
|
||||
+
|
||||
+
|
||||
def add_query_source(span):
|
||||
# type: (sentry_sdk.tracing.Span) -> None
|
||||
"""
|
||||
@@ -200,10 +208,7 @@ def add_query_source(span):
|
||||
# Find the correct frame
|
||||
frame = sys._getframe() # type: Union[FrameType, None]
|
||||
while frame is not None:
|
||||
- try:
|
||||
- abs_path = frame.f_code.co_filename
|
||||
- except Exception:
|
||||
- abs_path = ""
|
||||
+ abs_path = _get_frame_module_abs_path(frame)
|
||||
|
||||
try:
|
||||
namespace = frame.f_globals.get("__name__") # type: Optional[str]
|
||||
@@ -224,7 +229,8 @@ def add_query_source(span):
|
||||
should_be_included = True
|
||||
|
||||
if (
|
||||
- abs_path.startswith(project_root)
|
||||
+ abs_path is not None
|
||||
+ and abs_path.startswith(project_root)
|
||||
and should_be_included
|
||||
and not is_sentry_sdk_frame
|
||||
):
|
||||
@@ -250,10 +256,7 @@ def add_query_source(span):
|
||||
if namespace is not None:
|
||||
span.set_data(SPANDATA.CODE_NAMESPACE, namespace)
|
||||
|
||||
- try:
|
||||
- filepath = frame.f_code.co_filename
|
||||
- except Exception:
|
||||
- filepath = None
|
||||
+ filepath = _get_frame_module_abs_path(frame)
|
||||
if filepath is not None:
|
||||
if namespace is not None:
|
||||
in_app_path = filename_for_module(namespace, filepath)
|
||||
diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py
|
||||
index 8a805d3d..fcbff45e 100644
|
||||
--- a/sentry_sdk/utils.py
|
||||
+++ b/sentry_sdk/utils.py
|
||||
@@ -1058,8 +1058,11 @@ def _module_in_list(name, items):
|
||||
|
||||
|
||||
def _is_external_source(abs_path):
|
||||
- # type: (str) -> bool
|
||||
+ # type: (Optional[str]) -> bool
|
||||
# check if frame is in 'site-packages' or 'dist-packages'
|
||||
+ if abs_path is None:
|
||||
+ return False
|
||||
+
|
||||
external_source = (
|
||||
re.search(r"[\\/](?:dist|site)-packages[\\/]", abs_path) is not None
|
||||
)
|
||||
diff --git a/tests/integrations/django/test_db_query_data.py b/tests/integrations/django/test_db_query_data.py
|
||||
index 087fc5ad..265c6a79 100644
|
||||
--- a/tests/integrations/django/test_db_query_data.py
|
||||
+++ b/tests/integrations/django/test_db_query_data.py
|
||||
@@ -1,7 +1,9 @@
|
||||
+import contextlib
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
+from pathlib import Path, PurePosixPath, PureWindowsPath
|
||||
from unittest import mock
|
||||
|
||||
from django import VERSION as DJANGO_VERSION
|
||||
@@ -15,14 +17,20 @@ except ImportError:
|
||||
from werkzeug.test import Client
|
||||
|
||||
from sentry_sdk import start_transaction
|
||||
+from sentry_sdk._types import TYPE_CHECKING
|
||||
from sentry_sdk.consts import SPANDATA
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
-from sentry_sdk.tracing_utils import record_sql_queries
|
||||
+from sentry_sdk.tracing_utils import _get_frame_module_abs_path, record_sql_queries
|
||||
+from sentry_sdk.utils import _module_in_list
|
||||
|
||||
from tests.conftest import unpack_werkzeug_response
|
||||
from tests.integrations.django.utils import pytest_mark_django_db_decorator
|
||||
from tests.integrations.django.myapp.wsgi import application
|
||||
|
||||
+if TYPE_CHECKING:
|
||||
+ from types import FrameType
|
||||
+ from typing import Optional
|
||||
+
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
@@ -283,7 +291,10 @@ def test_query_source_with_in_app_exclude(sentry_init, client, capture_events):
|
||||
|
||||
@pytest.mark.forked
|
||||
@pytest_mark_django_db_decorator(transaction=True)
|
||||
-def test_query_source_with_in_app_include(sentry_init, client, capture_events):
|
||||
+@pytest.mark.parametrize("django_outside_of_project_root", [False, True])
|
||||
+def test_query_source_with_in_app_include(
|
||||
+ sentry_init, client, capture_events, django_outside_of_project_root
|
||||
+):
|
||||
sentry_init(
|
||||
integrations=[DjangoIntegration()],
|
||||
send_default_pii=True,
|
||||
@@ -301,8 +312,43 @@ def test_query_source_with_in_app_include(sentry_init, client, capture_events):
|
||||
|
||||
events = capture_events()
|
||||
|
||||
- _, status, _ = unpack_werkzeug_response(client.get(reverse("postgres_select_orm")))
|
||||
- assert status == "200 OK"
|
||||
+ # Simulate Django installation outside of the project root
|
||||
+ original_get_frame_module_abs_path = _get_frame_module_abs_path
|
||||
+
|
||||
+ def patched_get_frame_module_abs_path_function(frame):
|
||||
+ # type: (FrameType) -> Optional[str]
|
||||
+ result = original_get_frame_module_abs_path(frame)
|
||||
+ if result is None:
|
||||
+ return result
|
||||
+
|
||||
+ namespace = frame.f_globals.get("__name__")
|
||||
+ if _module_in_list(namespace, ["django"]):
|
||||
+ # Since the result is used as `str` only and not accessed,
|
||||
+ # it is sufficient to generate non-existent path
|
||||
+ # that would be located outside of the project root.
|
||||
+ # Use UNC path for simplicity on Windows.
|
||||
+ non_existent_prefix = (
|
||||
+ PureWindowsPath("//outside-of-project-root")
|
||||
+ if os.name == "nt"
|
||||
+ else PurePosixPath("/outside-of-project-root")
|
||||
+ )
|
||||
+ result = str(non_existent_prefix.joinpath(*Path(result).parts[1:]))
|
||||
+ return result
|
||||
+
|
||||
+ patched_get_frame_module_abs_path = (
|
||||
+ mock.patch(
|
||||
+ "sentry_sdk.tracing_utils._get_frame_module_abs_path",
|
||||
+ patched_get_frame_module_abs_path_function,
|
||||
+ )
|
||||
+ if django_outside_of_project_root
|
||||
+ else contextlib.suppress()
|
||||
+ )
|
||||
+
|
||||
+ with patched_get_frame_module_abs_path:
|
||||
+ _, status, _ = unpack_werkzeug_response(
|
||||
+ client.get(reverse("postgres_select_orm"))
|
||||
+ )
|
||||
+ assert status == "200 OK"
|
||||
|
||||
(event,) = events
|
||||
for span in event["spans"]:
|
||||
--
|
||||
2.45.2
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
From 7ff39563c20b83809ae2378479d0324880231b33 Mon Sep 17 00:00:00 2001
|
||||
From: Roman Inflianskas <rominf@pm.me>
|
||||
Date: Thu, 18 Jul 2024 16:57:21 +0300
|
||||
Subject: [PATCH 2/2] fix(tracing): Fix `add_query_source` with modules outside
|
||||
of project root
|
||||
|
||||
Fix: https://github.com/getsentry/sentry-python/issues/3312
|
||||
|
||||
Previously, when packages added in `in_app_include` were installed
|
||||
to a location outside of the project root directory, span from
|
||||
those packages were not extended with OTel compatible source code
|
||||
information. Cases include running Python from virtualenv created
|
||||
outside of the project root directory or Python packages installed into
|
||||
the system using package managers. This resulted in an inconsistency:
|
||||
spans from the same project would be different, depending on the
|
||||
deployment method.
|
||||
|
||||
In this change, the logic was slightly changed to avoid these
|
||||
discrepancies and conform to the requirements, described in the PR with
|
||||
better setting of in-app in stack frames:
|
||||
https://github.com/getsentry/sentry-python/pull/1894#issue-1579192436.
|
||||
Note that the `_module_in_list` function returns `False` if `name` is
|
||||
`None` or `items` are falsy, hence extra check before function call can
|
||||
be omitted to simplify code.
|
||||
---
|
||||
sentry_sdk/tracing_utils.py | 22 ++++++++--------------
|
||||
sentry_sdk/utils.py | 6 +++---
|
||||
2 files changed, 11 insertions(+), 17 deletions(-)
|
||||
|
||||
diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py
|
||||
index 47e14ecd..bfcf8d0a 100644
|
||||
--- a/sentry_sdk/tracing_utils.py
|
||||
+++ b/sentry_sdk/tracing_utils.py
|
||||
@@ -22,6 +22,7 @@ from sentry_sdk.utils import (
|
||||
is_sentry_url,
|
||||
_is_external_source,
|
||||
_module_in_list,
|
||||
+ _is_in_project_root,
|
||||
)
|
||||
from sentry_sdk._types import TYPE_CHECKING
|
||||
|
||||
@@ -218,21 +219,14 @@ def add_query_source(span):
|
||||
is_sentry_sdk_frame = namespace is not None and namespace.startswith(
|
||||
"sentry_sdk."
|
||||
)
|
||||
+ should_be_included = _module_in_list(namespace, in_app_include)
|
||||
+ should_be_excluded = _is_external_source(abs_path) or _module_in_list(
|
||||
+ namespace, in_app_exclude
|
||||
+ )
|
||||
|
||||
- should_be_included = not _is_external_source(abs_path)
|
||||
- if namespace is not None:
|
||||
- if in_app_exclude and _module_in_list(namespace, in_app_exclude):
|
||||
- should_be_included = False
|
||||
- if in_app_include and _module_in_list(namespace, in_app_include):
|
||||
- # in_app_include takes precedence over in_app_exclude, so doing it
|
||||
- # at the end
|
||||
- should_be_included = True
|
||||
-
|
||||
- if (
|
||||
- abs_path is not None
|
||||
- and abs_path.startswith(project_root)
|
||||
- and should_be_included
|
||||
- and not is_sentry_sdk_frame
|
||||
+ if not is_sentry_sdk_frame and (
|
||||
+ should_be_included
|
||||
+ or (_is_in_project_root(abs_path, project_root) and not should_be_excluded)
|
||||
):
|
||||
break
|
||||
|
||||
diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py
|
||||
index fcbff45e..22df7aef 100644
|
||||
--- a/sentry_sdk/utils.py
|
||||
+++ b/sentry_sdk/utils.py
|
||||
@@ -1043,7 +1043,7 @@ def event_from_exception(
|
||||
|
||||
|
||||
def _module_in_list(name, items):
|
||||
- # type: (str, Optional[List[str]]) -> bool
|
||||
+ # type: (Optional[str], Optional[List[str]]) -> bool
|
||||
if name is None:
|
||||
return False
|
||||
|
||||
@@ -1070,8 +1070,8 @@ def _is_external_source(abs_path):
|
||||
|
||||
|
||||
def _is_in_project_root(abs_path, project_root):
|
||||
- # type: (str, Optional[str]) -> bool
|
||||
- if project_root is None:
|
||||
+ # type: (Optional[str], Optional[str]) -> bool
|
||||
+ if abs_path is None or project_root is None:
|
||||
return False
|
||||
|
||||
# check if path is in the project root
|
||||
--
|
||||
2.45.2
|
||||
|
111
0001-tests-reorder-to-unpin-pytest.patch
Normal file
111
0001-tests-reorder-to-unpin-pytest.patch
Normal file
|
@ -0,0 +1,111 @@
|
|||
From 3ba07652d54b6fe45239c9c9b4ea1e9edac5c976 Mon Sep 17 00:00:00 2001
|
||||
From: Roman Inflianskas <rominf@pm.me>
|
||||
Date: Wed, 2 Oct 2024 14:15:13 +0300
|
||||
Subject: [PATCH] tests: reorder to unpin pytest
|
||||
|
||||
Fix: #3035
|
||||
|
||||
Separate forked tests from normal tests within each module during
|
||||
collection phase to avoid issues, caused by `pytest-forked`.
|
||||
|
||||
Workaround to unpin pytest. See:
|
||||
https://github.com/pytest-dev/pytest/issues/9621,
|
||||
https://github.com/pytest-dev/pytest-forked/issues/67, and specifically:
|
||||
https://github.com/pytest-dev/pytest-forked/issues/67#issuecomment-1964718720
|
||||
---
|
||||
requirements-devenv.txt | 1 -
|
||||
tests/conftest.py | 38 ++++++++++++++++++++++++++++++++++++++
|
||||
tox.ini | 10 ----------
|
||||
3 files changed, 38 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/requirements-devenv.txt b/requirements-devenv.txt
|
||||
index 29d3f15e..5696567a 100644
|
||||
--- a/requirements-devenv.txt
|
||||
+++ b/requirements-devenv.txt
|
||||
@@ -1,5 +1,4 @@
|
||||
-r requirements-linting.txt
|
||||
-r requirements-testing.txt
|
||||
mockupdb # required by `pymongo` tests that are enabled by `pymongo` from linter requirements
|
||||
-pytest<7.0.0 # https://github.com/pytest-dev/pytest/issues/9621; see tox.ini
|
||||
pytest-asyncio
|
||||
diff --git a/tests/conftest.py b/tests/conftest.py
|
||||
index 64527c1e..bad96e92 100644
|
||||
--- a/tests/conftest.py
|
||||
+++ b/tests/conftest.py
|
||||
@@ -645,3 +645,41 @@ class ApproxDict(dict):
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
+
|
||||
+
|
||||
+def pytest_collection_modifyitems(config, items):
|
||||
+ """
|
||||
+ Rearrange test items so that isolated tests come before normal tests within their respective modules.
|
||||
+ Swap the last isolated test with the last normal test if necessary.
|
||||
+
|
||||
+ Workaround to unpin pytest. See:
|
||||
+ https://github.com/pytest-dev/pytest/issues/9621,
|
||||
+ https://github.com/pytest-dev/pytest-forked/issues/67, and specifically:
|
||||
+ https://github.com/pytest-dev/pytest-forked/issues/67#issuecomment-1964718720
|
||||
+ """
|
||||
+ module_states = {}
|
||||
+
|
||||
+ for idx in range(len(items)):
|
||||
+ item = items[idx]
|
||||
+ current_module = item.module.__name__
|
||||
+
|
||||
+ if current_module not in module_states:
|
||||
+ module_states[current_module] = {"forked": [], "normal": []}
|
||||
+
|
||||
+ if "forked" in item.keywords:
|
||||
+ module_states[current_module]["forked"].append(idx)
|
||||
+ else:
|
||||
+ module_states[current_module]["normal"].append(idx)
|
||||
+
|
||||
+ # Swap the last forked test with the last normal test if necessary
|
||||
+ for states in module_states.values():
|
||||
+ if states["forked"] and states["normal"]:
|
||||
+ last_forked_idx = states["forked"][-1]
|
||||
+ last_normal_idx = states["normal"][-1]
|
||||
+
|
||||
+ if last_forked_idx > last_normal_idx:
|
||||
+ # Swap the items
|
||||
+ items[last_forked_idx], items[last_normal_idx] = (
|
||||
+ items[last_normal_idx],
|
||||
+ items[last_forked_idx],
|
||||
+ )
|
||||
diff --git a/tox.ini b/tox.ini
|
||||
index 2f351d7e..7af1c7b8 100644
|
||||
--- a/tox.ini
|
||||
+++ b/tox.ini
|
||||
@@ -290,19 +290,10 @@ deps =
|
||||
# === Common ===
|
||||
py3.8-common: hypothesis
|
||||
{py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12,py3.13}-common: pytest-asyncio
|
||||
- # See https://github.com/pytest-dev/pytest/issues/9621
|
||||
- # and https://github.com/pytest-dev/pytest-forked/issues/67
|
||||
- # for justification of the upper bound on pytest
|
||||
- {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12}-common: pytest<7.0.0
|
||||
- py3.13-common: pytest
|
||||
|
||||
# === Gevent ===
|
||||
{py3.6,py3.7,py3.8,py3.9,py3.10,py3.11}-gevent: gevent>=22.10.0, <22.11.0
|
||||
{py3.12}-gevent: gevent
|
||||
- # See https://github.com/pytest-dev/pytest/issues/9621
|
||||
- # and https://github.com/pytest-dev/pytest-forked/issues/67
|
||||
- # for justification of the upper bound on pytest
|
||||
- {py3.6,py3.7,py3.8,py3.9,py3.10,py3.11,py3.12}-gevent: pytest<7.0.0
|
||||
|
||||
# === Integrations ===
|
||||
|
||||
@@ -372,7 +363,6 @@ deps =
|
||||
celery-latest: Celery
|
||||
|
||||
celery: newrelic
|
||||
- celery: pytest<7
|
||||
{py3.7}-celery: importlib-metadata<5.0
|
||||
|
||||
# Chalice
|
||||
--
|
||||
2.46.2
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
%bcond network_tests 0
|
||||
|
||||
%global forgeurl https://github.com/getsentry/sentry-python
|
||||
Version: 2.13.0
|
||||
Version: 2.15.0
|
||||
%global tag %{version}
|
||||
%forgemeta
|
||||
|
||||
|
@ -54,10 +54,8 @@ Summary: The new Python SDK for Sentry.io
|
|||
License: MIT
|
||||
URL: https://sentry.io/for/python/
|
||||
Source0: %{forgesource}
|
||||
# Patches for testing and fixing logic for handling `in_app_include` in `add_query_source`
|
||||
# Upstream PR: https://github.com/getsentry/sentry-python/pull/3313
|
||||
Patch0: 0000-test-tracing-Test-add_query_source-with-modules-outs.patch
|
||||
Patch1: 0001-fix-tracing-Fix-add_query_source-with-modules-outsid.patch
|
||||
# Upstream PR: https://github.com/getsentry/sentry-python/pull/3598
|
||||
Patch0: 0001-tests-reorder-to-unpin-pytest.patch
|
||||
|
||||
BuildArch: noarch
|
||||
BuildRequires: python3-devel
|
||||
|
@ -269,7 +267,7 @@ diff <(echo "$defined_extra") <(echo "$setup_py_extra")
|
|||
sed -r -i 's/psycopg2-binary/psycopg2/' tox.ini
|
||||
|
||||
# Unpin all test dependencies to make the installation happen.
|
||||
sed -r -i 's/(pytest)<7\.0\.0/\1/' tox.ini
|
||||
sed -r -i 's/(pytest)<7.*/\1/' tox.ini
|
||||
sed -r -i 's/(Werkzeug)<2\.1\.0/\1/' tox.ini
|
||||
sed -r -i 's/(gevent)>=22\.10\.0, <22\.11\.0/\1/' tox.ini
|
||||
sed -r -i 's/(anyio)<4\.0\.0/\1/' tox.ini
|
||||
|
|
2
sources
2
sources
|
@ -1 +1 @@
|
|||
SHA512 (sentry-python-2.13.0.tar.gz) = 4dd48d8acd1a132d93e08bb44028b7ec88b75bb821acbc3376391527e113c38abd74b4bc535b9a1f79fa3647bbafb22de85a6a553dc2c61fbd6095ffb39c6f32
|
||||
SHA512 (sentry-python-2.15.0.tar.gz) = 975b45acecbea4c7e6a6eb5b77627f43a515f15e6ac3164bfbff49bf45fa5be0f9443a0c4f1ab9cc05b152707cdf0c3d97df3cf327daa2690a32b1b3f977c5ba
|
||||
|
|
Loading…
Add table
Reference in a new issue