apitally 0.19.0__tar.gz → 0.19.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {apitally-0.19.0 → apitally-0.19.1}/PKG-INFO +1 -1
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/sentry.py +10 -5
- {apitally-0.19.0 → apitally-0.19.1}/apitally/fastapi.py +2 -2
- {apitally-0.19.0 → apitally-0.19.1}/apitally/flask.py +5 -1
- {apitally-0.19.0 → apitally-0.19.1}/apitally/litestar.py +5 -1
- {apitally-0.19.0 → apitally-0.19.1}/apitally/starlette.py +5 -1
- apitally-0.19.1/tests/test_client_sentry.py +47 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_flask.py +3 -3
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_litestar.py +2 -2
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_starlette.py +2 -2
- {apitally-0.19.0 → apitally-0.19.1}/.github/workflows/publish.yaml +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/.github/workflows/summary.yaml +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/.github/workflows/tests.yaml +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/.gitignore +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/.pre-commit-config.yaml +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/.tool-versions +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/LICENSE +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/Makefile +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/README.md +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/__init__.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/blacksheep.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/__init__.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/client_asyncio.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/client_base.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/client_threading.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/consumers.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/logging.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/request_logging.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/requests.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/server_errors.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/client/validation_errors.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/common.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/django.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/django_ninja.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/django_rest_framework.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/apitally/py.typed +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/pyproject.toml +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/renovate.json +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/__init__.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/conftest.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/constants.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/django_ninja_urls.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/django_rest_framework_urls.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_blacksheep.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_asyncio.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_consumers.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_request_logging.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_requests.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_server_errors.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_threading.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_validation_errors.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_django_ninja.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_django_rest_framework.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/tests/test_fastapi.py +0 -0
- {apitally-0.19.0 → apitally-0.19.1}/uv.lock +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: apitally
|
3
|
-
Version: 0.19.
|
3
|
+
Version: 0.19.1
|
4
4
|
Summary: Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette, Litestar and BlackSheep.
|
5
5
|
Project-URL: Homepage, https://apitally.io
|
6
6
|
Project-URL: Documentation, https://docs.apitally.io
|
@@ -6,17 +6,22 @@ from typing import Callable, Set
|
|
6
6
|
_tasks: Set[asyncio.Task] = set()
|
7
7
|
|
8
8
|
|
9
|
-
def get_sentry_event_id_async(cb: Callable[[str], None]) -> None:
|
9
|
+
def get_sentry_event_id_async(cb: Callable[[str], None], raise_on_error: bool = False) -> None:
|
10
10
|
try:
|
11
|
-
|
11
|
+
import sentry_sdk
|
12
12
|
from sentry_sdk.scope import Scope
|
13
13
|
except ImportError:
|
14
|
+
if raise_on_error:
|
15
|
+
raise
|
14
16
|
return # pragma: no cover
|
15
17
|
if not hasattr(Scope, "get_isolation_scope") or not hasattr(Scope, "_last_event_id"):
|
16
|
-
|
18
|
+
if raise_on_error:
|
19
|
+
raise RuntimeError("sentry-sdk < 2.2.0 is not supported")
|
17
20
|
return # pragma: no cover
|
18
|
-
if
|
19
|
-
|
21
|
+
if not sentry_sdk.is_initialized():
|
22
|
+
if raise_on_error:
|
23
|
+
raise RuntimeError("sentry-sdk not initialized")
|
24
|
+
return
|
20
25
|
|
21
26
|
scope = Scope.get_isolation_scope()
|
22
27
|
if event_id := scope._last_event_id:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from apitally.starlette import ApitallyConsumer, ApitallyMiddleware, RequestLoggingConfig
|
1
|
+
from apitally.starlette import ApitallyConsumer, ApitallyMiddleware, RequestLoggingConfig, set_consumer
|
2
2
|
|
3
3
|
|
4
|
-
__all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig"]
|
4
|
+
__all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig", "set_consumer"]
|
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
|
|
28
28
|
from werkzeug.routing.map import Map
|
29
29
|
|
30
30
|
|
31
|
-
__all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig"]
|
31
|
+
__all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig", "set_consumer"]
|
32
32
|
|
33
33
|
|
34
34
|
class ApitallyMiddleware:
|
@@ -216,6 +216,10 @@ class ApitallyMiddleware:
|
|
216
216
|
return None
|
217
217
|
|
218
218
|
|
219
|
+
def set_consumer(identifier: str, name: Optional[str] = None, group: Optional[str] = None) -> None:
|
220
|
+
g.apitally_consumer = ApitallyConsumer(identifier, name=name, group=group)
|
221
|
+
|
222
|
+
|
219
223
|
def _get_startup_data(
|
220
224
|
app: Flask, app_version: Optional[str] = None, openapi_url: Optional[str] = None
|
221
225
|
) -> Dict[str, Any]:
|
@@ -24,7 +24,7 @@ from apitally.client.request_logging import (
|
|
24
24
|
from apitally.common import get_versions, parse_int, try_json_loads
|
25
25
|
|
26
26
|
|
27
|
-
__all__ = ["ApitallyPlugin", "ApitallyConsumer", "RequestLoggingConfig"]
|
27
|
+
__all__ = ["ApitallyPlugin", "ApitallyConsumer", "RequestLoggingConfig", "set_consumer"]
|
28
28
|
|
29
29
|
|
30
30
|
class ApitallyPlugin(InitPluginProtocol):
|
@@ -287,6 +287,10 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
287
287
|
return None
|
288
288
|
|
289
289
|
|
290
|
+
def set_consumer(request: Request, identifier: str, name: Optional[str] = None, group: Optional[str] = None) -> None:
|
291
|
+
request.state.apitally_consumer = ApitallyConsumer(identifier, name=name, group=group)
|
292
|
+
|
293
|
+
|
290
294
|
def _get_openapi(app: Litestar) -> str:
|
291
295
|
schema = app.openapi_schema.to_schema()
|
292
296
|
return json.dumps(schema)
|
@@ -25,7 +25,7 @@ from apitally.client.request_logging import (
|
|
25
25
|
from apitally.common import get_versions, parse_int, try_json_loads
|
26
26
|
|
27
27
|
|
28
|
-
__all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig"]
|
28
|
+
__all__ = ["ApitallyMiddleware", "ApitallyConsumer", "RequestLoggingConfig", "set_consumer"]
|
29
29
|
|
30
30
|
|
31
31
|
class ApitallyMiddleware:
|
@@ -268,6 +268,10 @@ class ApitallyMiddleware:
|
|
268
268
|
return None
|
269
269
|
|
270
270
|
|
271
|
+
def set_consumer(request: Request, identifier: str, name: Optional[str] = None, group: Optional[str] = None) -> None:
|
272
|
+
request.state.apitally_consumer = ApitallyConsumer(identifier, name=name, group=group)
|
273
|
+
|
274
|
+
|
271
275
|
def _get_startup_data(
|
272
276
|
app: ASGIApp, app_version: Optional[str] = None, openapi_url: Optional[str] = None
|
273
277
|
) -> Dict[str, Any]:
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import time
|
2
|
+
from importlib.util import find_spec
|
3
|
+
|
4
|
+
import pytest
|
5
|
+
|
6
|
+
|
7
|
+
if find_spec("sentry_sdk") is None:
|
8
|
+
pytest.skip("sentry-sdk is not available", allow_module_level=True)
|
9
|
+
|
10
|
+
|
11
|
+
def test_get_sentry_event_id_async():
|
12
|
+
import sentry_sdk
|
13
|
+
from sentry_sdk.transport import Transport
|
14
|
+
|
15
|
+
from apitally.client.sentry import get_sentry_event_id_async
|
16
|
+
|
17
|
+
with pytest.raises(RuntimeError, match="not initialized"):
|
18
|
+
get_sentry_event_id_async(lambda _: None, raise_on_error=True)
|
19
|
+
|
20
|
+
class MockTransport(Transport):
|
21
|
+
def __init__(self):
|
22
|
+
super().__init__()
|
23
|
+
self.events = []
|
24
|
+
|
25
|
+
def capture_envelope(self, envelope):
|
26
|
+
self.events.append(envelope)
|
27
|
+
|
28
|
+
transport = MockTransport()
|
29
|
+
sentry_sdk.init(
|
30
|
+
dsn="https://1234567890@sentry.io/1234567890",
|
31
|
+
transport=transport,
|
32
|
+
auto_enabling_integrations=False,
|
33
|
+
)
|
34
|
+
|
35
|
+
event_id = None
|
36
|
+
|
37
|
+
def callback(event_id_: str) -> None:
|
38
|
+
nonlocal event_id
|
39
|
+
event_id = event_id_
|
40
|
+
|
41
|
+
sentry_sdk.capture_message("test")
|
42
|
+
get_sentry_event_id_async(callback, raise_on_error=True)
|
43
|
+
time.sleep(0.01)
|
44
|
+
|
45
|
+
assert event_id is not None
|
46
|
+
assert len(transport.events) == 1
|
47
|
+
assert event_id == transport.events[0].items[0].payload.json["event_id"]
|
@@ -18,9 +18,9 @@ if TYPE_CHECKING:
|
|
18
18
|
|
19
19
|
@pytest.fixture(scope="module")
|
20
20
|
def app(module_mocker: MockerFixture) -> Flask:
|
21
|
-
from flask import Flask,
|
21
|
+
from flask import Flask, request
|
22
22
|
|
23
|
-
from apitally.flask import ApitallyMiddleware, RequestLoggingConfig
|
23
|
+
from apitally.flask import ApitallyMiddleware, RequestLoggingConfig, set_consumer
|
24
24
|
|
25
25
|
module_mocker.patch("apitally.client.client_threading.ApitallyClient._instance", None)
|
26
26
|
module_mocker.patch("apitally.client.client_threading.ApitallyClient.start_sync_loop")
|
@@ -41,7 +41,7 @@ def app(module_mocker: MockerFixture) -> Flask:
|
|
41
41
|
|
42
42
|
@app.route("/foo/<bar>")
|
43
43
|
def foo_bar(bar: int):
|
44
|
-
|
44
|
+
set_consumer("test")
|
45
45
|
return f"foo: {bar}", {"Content-Type": "text/plain"}
|
46
46
|
|
47
47
|
@app.route("/bar", methods=["POST"])
|
@@ -28,7 +28,7 @@ async def app(module_mocker: MockerFixture) -> Litestar:
|
|
28
28
|
from litestar.handlers import get, post
|
29
29
|
from litestar.response import Stream
|
30
30
|
|
31
|
-
from apitally.litestar import ApitallyConsumer, ApitallyPlugin, RequestLoggingConfig
|
31
|
+
from apitally.litestar import ApitallyConsumer, ApitallyPlugin, RequestLoggingConfig, set_consumer
|
32
32
|
|
33
33
|
async def mocked_handle_shutdown(_):
|
34
34
|
# Empty function instead of Mock to avoid the following error in Python 3.10:
|
@@ -45,7 +45,7 @@ async def app(module_mocker: MockerFixture) -> Litestar:
|
|
45
45
|
|
46
46
|
@get("/foo/{bar:str}")
|
47
47
|
async def foo_bar(request: Request, bar: str) -> str:
|
48
|
-
request
|
48
|
+
set_consumer(request, "test2")
|
49
49
|
return f"foo: {bar}"
|
50
50
|
|
51
51
|
@post("/bar")
|
@@ -41,10 +41,10 @@ def get_starlette_app() -> Starlette:
|
|
41
41
|
from starlette.responses import PlainTextResponse, StreamingResponse
|
42
42
|
from starlette.routing import Mount, Route
|
43
43
|
|
44
|
-
from apitally.starlette import ApitallyConsumer, ApitallyMiddleware, RequestLoggingConfig
|
44
|
+
from apitally.starlette import ApitallyConsumer, ApitallyMiddleware, RequestLoggingConfig, set_consumer
|
45
45
|
|
46
46
|
def foo(request: Request):
|
47
|
-
request
|
47
|
+
set_consumer(request, "test")
|
48
48
|
return PlainTextResponse("foo")
|
49
49
|
|
50
50
|
def foo_bar(request: Request):
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|