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.
Files changed (55) hide show
  1. {apitally-0.19.0 → apitally-0.19.1}/PKG-INFO +1 -1
  2. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/sentry.py +10 -5
  3. {apitally-0.19.0 → apitally-0.19.1}/apitally/fastapi.py +2 -2
  4. {apitally-0.19.0 → apitally-0.19.1}/apitally/flask.py +5 -1
  5. {apitally-0.19.0 → apitally-0.19.1}/apitally/litestar.py +5 -1
  6. {apitally-0.19.0 → apitally-0.19.1}/apitally/starlette.py +5 -1
  7. apitally-0.19.1/tests/test_client_sentry.py +47 -0
  8. {apitally-0.19.0 → apitally-0.19.1}/tests/test_flask.py +3 -3
  9. {apitally-0.19.0 → apitally-0.19.1}/tests/test_litestar.py +2 -2
  10. {apitally-0.19.0 → apitally-0.19.1}/tests/test_starlette.py +2 -2
  11. {apitally-0.19.0 → apitally-0.19.1}/.github/workflows/publish.yaml +0 -0
  12. {apitally-0.19.0 → apitally-0.19.1}/.github/workflows/summary.yaml +0 -0
  13. {apitally-0.19.0 → apitally-0.19.1}/.github/workflows/tests.yaml +0 -0
  14. {apitally-0.19.0 → apitally-0.19.1}/.gitignore +0 -0
  15. {apitally-0.19.0 → apitally-0.19.1}/.pre-commit-config.yaml +0 -0
  16. {apitally-0.19.0 → apitally-0.19.1}/.tool-versions +0 -0
  17. {apitally-0.19.0 → apitally-0.19.1}/LICENSE +0 -0
  18. {apitally-0.19.0 → apitally-0.19.1}/Makefile +0 -0
  19. {apitally-0.19.0 → apitally-0.19.1}/README.md +0 -0
  20. {apitally-0.19.0 → apitally-0.19.1}/apitally/__init__.py +0 -0
  21. {apitally-0.19.0 → apitally-0.19.1}/apitally/blacksheep.py +0 -0
  22. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/__init__.py +0 -0
  23. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/client_asyncio.py +0 -0
  24. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/client_base.py +0 -0
  25. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/client_threading.py +0 -0
  26. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/consumers.py +0 -0
  27. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/logging.py +0 -0
  28. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/request_logging.py +0 -0
  29. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/requests.py +0 -0
  30. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/server_errors.py +0 -0
  31. {apitally-0.19.0 → apitally-0.19.1}/apitally/client/validation_errors.py +0 -0
  32. {apitally-0.19.0 → apitally-0.19.1}/apitally/common.py +0 -0
  33. {apitally-0.19.0 → apitally-0.19.1}/apitally/django.py +0 -0
  34. {apitally-0.19.0 → apitally-0.19.1}/apitally/django_ninja.py +0 -0
  35. {apitally-0.19.0 → apitally-0.19.1}/apitally/django_rest_framework.py +0 -0
  36. {apitally-0.19.0 → apitally-0.19.1}/apitally/py.typed +0 -0
  37. {apitally-0.19.0 → apitally-0.19.1}/pyproject.toml +0 -0
  38. {apitally-0.19.0 → apitally-0.19.1}/renovate.json +0 -0
  39. {apitally-0.19.0 → apitally-0.19.1}/tests/__init__.py +0 -0
  40. {apitally-0.19.0 → apitally-0.19.1}/tests/conftest.py +0 -0
  41. {apitally-0.19.0 → apitally-0.19.1}/tests/constants.py +0 -0
  42. {apitally-0.19.0 → apitally-0.19.1}/tests/django_ninja_urls.py +0 -0
  43. {apitally-0.19.0 → apitally-0.19.1}/tests/django_rest_framework_urls.py +0 -0
  44. {apitally-0.19.0 → apitally-0.19.1}/tests/test_blacksheep.py +0 -0
  45. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_asyncio.py +0 -0
  46. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_consumers.py +0 -0
  47. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_request_logging.py +0 -0
  48. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_requests.py +0 -0
  49. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_server_errors.py +0 -0
  50. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_threading.py +0 -0
  51. {apitally-0.19.0 → apitally-0.19.1}/tests/test_client_validation_errors.py +0 -0
  52. {apitally-0.19.0 → apitally-0.19.1}/tests/test_django_ninja.py +0 -0
  53. {apitally-0.19.0 → apitally-0.19.1}/tests/test_django_rest_framework.py +0 -0
  54. {apitally-0.19.0 → apitally-0.19.1}/tests/test_fastapi.py +0 -0
  55. {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.0
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
- from sentry_sdk.hub import Hub
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
- # sentry-sdk < 2.2.0 is not supported
18
+ if raise_on_error:
19
+ raise RuntimeError("sentry-sdk < 2.2.0 is not supported")
17
20
  return # pragma: no cover
18
- if Hub.current.client is None:
19
- return # sentry-sdk not initialized
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, g, request
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
- g.apitally_consumer = "test"
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.state.apitally_consumer = "test2"
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.state.apitally_consumer = "test"
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