apitally 0.8.0__py3-none-any.whl → 0.10.0__py3-none-any.whl
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/__init__.py +1 -1
- apitally/client/base.py +34 -0
- apitally/django.py +6 -4
- apitally/flask.py +5 -1
- apitally/litestar.py +6 -4
- apitally/starlette.py +6 -4
- {apitally-0.8.0.dist-info → apitally-0.10.0.dist-info}/METADATA +9 -7
- apitally-0.10.0.dist-info/RECORD +19 -0
- apitally-0.8.0.dist-info/RECORD +0 -19
- {apitally-0.8.0.dist-info → apitally-0.10.0.dist-info}/LICENSE +0 -0
- {apitally-0.8.0.dist-info → apitally-0.10.0.dist-info}/WHEEL +0 -0
apitally/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.
|
1
|
+
__version__ = "0.10.0"
|
apitally/client/base.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import contextlib
|
4
5
|
import os
|
5
6
|
import re
|
@@ -243,6 +244,7 @@ class ServerError:
|
|
243
244
|
class ServerErrorCounter:
|
244
245
|
def __init__(self) -> None:
|
245
246
|
self.error_counts: Counter[ServerError] = Counter()
|
247
|
+
self.sentry_event_ids: Dict[ServerError, str] = {}
|
246
248
|
self._lock = threading.Lock()
|
247
249
|
|
248
250
|
def add_server_error(self, consumer: Optional[str], method: str, path: str, exception: BaseException) -> None:
|
@@ -259,6 +261,36 @@ class ServerErrorCounter:
|
|
259
261
|
traceback=self._get_truncated_exception_traceback(exception),
|
260
262
|
)
|
261
263
|
self.error_counts[server_error] += 1
|
264
|
+
self.capture_sentry_event_id(server_error)
|
265
|
+
|
266
|
+
def capture_sentry_event_id(self, server_error: ServerError) -> None:
|
267
|
+
try:
|
268
|
+
from sentry_sdk.hub import Hub
|
269
|
+
from sentry_sdk.scope import Scope
|
270
|
+
except ImportError:
|
271
|
+
return # pragma: no cover
|
272
|
+
if not hasattr(Scope, "get_isolation_scope") or not hasattr(Scope, "last_event_id"):
|
273
|
+
# sentry-sdk < 2.2.0 is not supported
|
274
|
+
return # pragma: no cover
|
275
|
+
if Hub.current.client is None:
|
276
|
+
return # sentry-sdk not initialized
|
277
|
+
|
278
|
+
scope = Scope.get_isolation_scope()
|
279
|
+
if event_id := scope.last_event_id():
|
280
|
+
self.sentry_event_ids[server_error] = event_id
|
281
|
+
return
|
282
|
+
|
283
|
+
async def _wait_for_sentry_event_id(scope: Scope) -> None:
|
284
|
+
i = 0
|
285
|
+
while not (event_id := scope.last_event_id()) and i < 100:
|
286
|
+
i += 1
|
287
|
+
await asyncio.sleep(0.001)
|
288
|
+
if event_id:
|
289
|
+
self.sentry_event_ids[server_error] = event_id
|
290
|
+
|
291
|
+
with contextlib.suppress(RuntimeError): # ignore no running loop
|
292
|
+
loop = asyncio.get_running_loop()
|
293
|
+
loop.create_task(_wait_for_sentry_event_id(scope))
|
262
294
|
|
263
295
|
def get_and_reset_server_errors(self) -> List[Dict[str, Any]]:
|
264
296
|
data: List[Dict[str, Any]] = []
|
@@ -272,10 +304,12 @@ class ServerErrorCounter:
|
|
272
304
|
"type": server_error.type,
|
273
305
|
"msg": server_error.msg,
|
274
306
|
"traceback": server_error.traceback,
|
307
|
+
"sentry_event_id": self.sentry_event_ids.get(server_error),
|
275
308
|
"error_count": count,
|
276
309
|
}
|
277
310
|
)
|
278
311
|
self.error_counts.clear()
|
312
|
+
self.sentry_event_ids.clear()
|
279
313
|
return data
|
280
314
|
|
281
315
|
@staticmethod
|
apitally/django.py
CHANGED
@@ -164,12 +164,14 @@ class ApitallyMiddleware:
|
|
164
164
|
|
165
165
|
def get_consumer(self, request: HttpRequest) -> Optional[str]:
|
166
166
|
try:
|
167
|
-
if hasattr(request, "
|
167
|
+
if hasattr(request, "apitally_consumer"):
|
168
|
+
return str(request.apitally_consumer)
|
169
|
+
if hasattr(request, "consumer_identifier"): # Keeping this for legacy support
|
168
170
|
return str(request.consumer_identifier)
|
169
171
|
if self.config is not None and self.config.identify_consumer_callback is not None:
|
170
|
-
|
171
|
-
if
|
172
|
-
return str(
|
172
|
+
consumer = self.config.identify_consumer_callback(request)
|
173
|
+
if consumer is not None:
|
174
|
+
return str(consumer)
|
173
175
|
except Exception: # pragma: no cover
|
174
176
|
logger.exception("Failed to get consumer identifier for request")
|
175
177
|
return None
|
apitally/flask.py
CHANGED
@@ -115,7 +115,11 @@ class ApitallyMiddleware:
|
|
115
115
|
return environ["PATH_INFO"], False
|
116
116
|
|
117
117
|
def get_consumer(self) -> Optional[str]:
|
118
|
-
|
118
|
+
if "apitally_consumer" in g:
|
119
|
+
return str(g.apitally_consumer)
|
120
|
+
if "consumer_identifier" in g: # Keeping this for legacy support
|
121
|
+
return str(g.consumer_identifier)
|
122
|
+
return None
|
119
123
|
|
120
124
|
|
121
125
|
def _get_app_info(app: Flask, app_version: Optional[str] = None, openapi_url: Optional[str] = None) -> Dict[str, Any]:
|
apitally/litestar.py
CHANGED
@@ -168,12 +168,14 @@ class ApitallyPlugin(InitPluginProtocol):
|
|
168
168
|
return False # pragma: no cover
|
169
169
|
|
170
170
|
def get_consumer(self, request: Request) -> Optional[str]:
|
171
|
-
if hasattr(request.state, "
|
171
|
+
if hasattr(request.state, "apitally_consumer"):
|
172
|
+
return str(request.state.apitally_consumer)
|
173
|
+
if hasattr(request.state, "consumer_identifier"): # Keeping this for legacy support
|
172
174
|
return str(request.state.consumer_identifier)
|
173
175
|
if self.identify_consumer_callback is not None:
|
174
|
-
|
175
|
-
if
|
176
|
-
return str(
|
176
|
+
consumer = self.identify_consumer_callback(request)
|
177
|
+
if consumer is not None:
|
178
|
+
return str(consumer)
|
177
179
|
return None
|
178
180
|
|
179
181
|
|
apitally/starlette.py
CHANGED
@@ -142,12 +142,14 @@ class ApitallyMiddleware(BaseHTTPMiddleware):
|
|
142
142
|
return request.url.path, False
|
143
143
|
|
144
144
|
def get_consumer(self, request: Request) -> Optional[str]:
|
145
|
-
if hasattr(request.state, "
|
145
|
+
if hasattr(request.state, "apitally_consumer"):
|
146
|
+
return str(request.state.apitally_consumer)
|
147
|
+
if hasattr(request.state, "consumer_identifier"): # Keeping this for legacy support
|
146
148
|
return str(request.state.consumer_identifier)
|
147
149
|
if self.identify_consumer_callback is not None:
|
148
|
-
|
149
|
-
if
|
150
|
-
return str(
|
150
|
+
consumer = self.identify_consumer_callback(request)
|
151
|
+
if consumer is not None:
|
152
|
+
return str(consumer)
|
151
153
|
return None
|
152
154
|
|
153
155
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: apitally
|
3
|
-
Version: 0.
|
4
|
-
Summary: API monitoring for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar.
|
3
|
+
Version: 0.10.0
|
4
|
+
Summary: Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar.
|
5
5
|
Home-page: https://apitally.io
|
6
6
|
License: MIT
|
7
7
|
Author: Apitally
|
@@ -26,6 +26,7 @@ Provides-Extra: django-rest-framework
|
|
26
26
|
Provides-Extra: fastapi
|
27
27
|
Provides-Extra: flask
|
28
28
|
Provides-Extra: litestar
|
29
|
+
Provides-Extra: sentry
|
29
30
|
Provides-Extra: starlette
|
30
31
|
Requires-Dist: backoff (>=2.0.0)
|
31
32
|
Requires-Dist: django (>=2.2) ; extra == "django-ninja" or extra == "django-rest-framework"
|
@@ -37,23 +38,24 @@ Requires-Dist: httpx (>=0.22.0) ; extra == "fastapi" or extra == "litestar" or e
|
|
37
38
|
Requires-Dist: inflection (>=0.5.1) ; extra == "django-rest-framework"
|
38
39
|
Requires-Dist: litestar (>=2.0.0) ; extra == "litestar"
|
39
40
|
Requires-Dist: requests (>=2.26.0) ; extra == "django-ninja" or extra == "django-rest-framework" or extra == "flask"
|
41
|
+
Requires-Dist: sentry-sdk (>=2.2.0) ; extra == "sentry"
|
40
42
|
Requires-Dist: starlette (>=0.21.0,<1.0.0) ; extra == "fastapi" or extra == "starlette"
|
41
43
|
Requires-Dist: uritemplate (>=3.0.0) ; extra == "django-rest-framework"
|
42
44
|
Project-URL: Documentation, https://docs.apitally.io
|
43
|
-
Project-URL: Repository, https://github.com/apitally/
|
45
|
+
Project-URL: Repository, https://github.com/apitally/apitally-py
|
44
46
|
Description-Content-Type: text/markdown
|
45
47
|
|
46
48
|
<p align="center">
|
47
49
|
<picture>
|
48
50
|
<source media="(prefers-color-scheme: dark)" srcset="https://assets.apitally.io/logos/logo-vertical-dark.png">
|
49
51
|
<source media="(prefers-color-scheme: light)" srcset="https://assets.apitally.io/logos/logo-vertical-light.png">
|
50
|
-
<img alt="Apitally logo" src="https://assets.apitally.io/logos/logo-vertical-light.png">
|
52
|
+
<img alt="Apitally logo" src="https://assets.apitally.io/logos/logo-vertical-light.png" width="150">
|
51
53
|
</picture>
|
52
54
|
</p>
|
53
55
|
|
54
56
|
<p align="center"><b>API monitoring made easy.</b></p>
|
55
57
|
|
56
|
-
<p align="center"><i>Apitally is a simple
|
58
|
+
<p align="center"><i>Apitally is a simple API monitoring & analytics tool with a focus on data privacy.<br>It is super easy to use for API projects in Python or Node.js and never collects sensitive data.</i></p>
|
57
59
|
|
58
60
|
<p align="center">🔗 <b><a href="https://apitally.io" target="_blank">apitally.io</a></b></p>
|
59
61
|
|
@@ -63,8 +65,8 @@ Description-Content-Type: text/markdown
|
|
63
65
|
|
64
66
|
# Apitally client library for Python
|
65
67
|
|
66
|
-
[](https://github.com/apitally/apitally-py/actions)
|
69
|
+
[](https://codecov.io/gh/apitally/apitally-py)
|
68
70
|
[](https://pypi.org/project/apitally/)
|
69
71
|
|
70
72
|
This client library for Apitally currently supports the following Python web
|
@@ -0,0 +1,19 @@
|
|
1
|
+
apitally/__init__.py,sha256=v4zmKjsKOPZbp6BrWoz7iK4ST0sdZdUh9bQSJmluZ5o,23
|
2
|
+
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
apitally/client/asyncio.py,sha256=uR5JlH37G6gZvAJ7A1gYOGkjn3zjC-4I6avA1fncXHs,4433
|
4
|
+
apitally/client/base.py,sha256=VWtHTkA6UT5aV37i_CXUvcfmYrabjHGBhzbDU6EeH1A,12785
|
5
|
+
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
6
|
+
apitally/client/threading.py,sha256=ihQzUStrSQFynpqXgFpseAXrHuc5Et1QvG-YHlzqDr8,4831
|
7
|
+
apitally/common.py,sha256=GbVmnXxhRvV30d7CfCQ9r0AeXj14Mv9Jm_Yd1bRWP28,1088
|
8
|
+
apitally/django.py,sha256=ZsCKTUq4V3nCwIcrO8_3mPQFuPRiriqRSxQ8IPMRoOQ,12922
|
9
|
+
apitally/django_ninja.py,sha256=iMvZd7j04nbOLpJgYxs7tpbsyXlZuhmHjcswXMvyUlU,82
|
10
|
+
apitally/django_rest_framework.py,sha256=iMvZd7j04nbOLpJgYxs7tpbsyXlZuhmHjcswXMvyUlU,82
|
11
|
+
apitally/fastapi.py,sha256=Q3n2bVREKQ_V_2yCQ48ngPtr-NJxDskpT_l20xhSbpM,85
|
12
|
+
apitally/flask.py,sha256=iD2mbFqrWoEFFmDNXUtR32OYwlHob4I3tJT90-kwcnw,5691
|
13
|
+
apitally/litestar.py,sha256=fgpdIlOpXS7TYFM02GUXk78H8WuJgDWMePPsQf-Pw1I,8092
|
14
|
+
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
apitally/starlette.py,sha256=e0PoLTrihHioLGDF1TThjlsiyYtusa6Or6GKCX7Y9NQ,7881
|
16
|
+
apitally-0.10.0.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
17
|
+
apitally-0.10.0.dist-info/METADATA,sha256=qS7p_ikNFk2H474nD7yI6KdZ5jPM3IuC7EQ5F9Nrbas,6834
|
18
|
+
apitally-0.10.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
19
|
+
apitally-0.10.0.dist-info/RECORD,,
|
apitally-0.8.0.dist-info/RECORD
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
apitally/__init__.py,sha256=iPlYCcIzuzW7T2HKDkmYlMkRI51dBLfNRxPPiWrfw9U,22
|
2
|
-
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
apitally/client/asyncio.py,sha256=uR5JlH37G6gZvAJ7A1gYOGkjn3zjC-4I6avA1fncXHs,4433
|
4
|
-
apitally/client/base.py,sha256=-2Bw_zFq6de-mSQSYsTa0NSRoMcHwiZqpSCQnjjIZ2M,11351
|
5
|
-
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
6
|
-
apitally/client/threading.py,sha256=ihQzUStrSQFynpqXgFpseAXrHuc5Et1QvG-YHlzqDr8,4831
|
7
|
-
apitally/common.py,sha256=GbVmnXxhRvV30d7CfCQ9r0AeXj14Mv9Jm_Yd1bRWP28,1088
|
8
|
-
apitally/django.py,sha256=Ym590Tiz1Qnk-IgFcna8r-XkSgcln2riziSPZCwD9nw,12812
|
9
|
-
apitally/django_ninja.py,sha256=iMvZd7j04nbOLpJgYxs7tpbsyXlZuhmHjcswXMvyUlU,82
|
10
|
-
apitally/django_rest_framework.py,sha256=iMvZd7j04nbOLpJgYxs7tpbsyXlZuhmHjcswXMvyUlU,82
|
11
|
-
apitally/fastapi.py,sha256=Q3n2bVREKQ_V_2yCQ48ngPtr-NJxDskpT_l20xhSbpM,85
|
12
|
-
apitally/flask.py,sha256=Utn92aXXl_1f4bKvuf4iZDB4v1vVLpeW5p1tF57Kf-8,5552
|
13
|
-
apitally/litestar.py,sha256=1-skfFDKjYa7y6mOdNvjR4YGtsQbNA0iGQP1jyre41Q,7978
|
14
|
-
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
apitally/starlette.py,sha256=kyXgzw0L90GUOZCgjuwM3q0uOpPX-hD0TQb2Wgqbqb8,7767
|
16
|
-
apitally-0.8.0.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
17
|
-
apitally-0.8.0.dist-info/METADATA,sha256=jl6_hdLWMu-CStH70iJ-4-Lt94328tX_X33avx-PIqI,6736
|
18
|
-
apitally-0.8.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
19
|
-
apitally-0.8.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|