apitally 0.11.1__py3-none-any.whl → 0.11.3__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/asyncio.py +13 -8
- apitally/client/base.py +7 -4
- apitally/client/threading.py +10 -6
- apitally/django.py +1 -1
- apitally/flask.py +1 -1
- apitally/starlette.py +5 -2
- {apitally-0.11.1.dist-info → apitally-0.11.3.dist-info}/METADATA +1 -1
- apitally-0.11.3.dist-info/RECORD +19 -0
- apitally-0.11.1.dist-info/RECORD +0 -19
- {apitally-0.11.1.dist-info → apitally-0.11.3.dist-info}/LICENSE +0 -0
- {apitally-0.11.1.dist-info → apitally-0.11.3.dist-info}/WHEEL +0 -0
apitally/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.11.
|
1
|
+
__version__ = "0.11.3"
|
apitally/client/asyncio.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import asyncio
|
4
4
|
import logging
|
5
|
+
import random
|
5
6
|
import time
|
6
7
|
from functools import partial
|
7
8
|
from typing import Any, Dict, Optional, Tuple
|
@@ -28,8 +29,9 @@ class ApitallyClient(ApitallyClientBase):
|
|
28
29
|
def __init__(self, client_id: str, env: str) -> None:
|
29
30
|
super().__init__(client_id=client_id, env=env)
|
30
31
|
self._stop_sync_loop = False
|
31
|
-
self._sync_loop_task: Optional[asyncio.Task
|
32
|
+
self._sync_loop_task: Optional[asyncio.Task] = None
|
32
33
|
self._sync_data_queue: asyncio.Queue[Tuple[float, Dict[str, Any]]] = asyncio.Queue()
|
34
|
+
self._set_startup_data_task: Optional[asyncio.Task] = None
|
33
35
|
|
34
36
|
def get_http_client(self) -> httpx.AsyncClient:
|
35
37
|
return httpx.AsyncClient(base_url=self.hub_url, timeout=REQUEST_TIMEOUT)
|
@@ -67,9 +69,9 @@ class ApitallyClient(ApitallyClientBase):
|
|
67
69
|
def set_startup_data(self, data: Dict[str, Any]) -> None:
|
68
70
|
self._startup_data_sent = False
|
69
71
|
self._startup_data = self.add_uuids_to_data(data)
|
70
|
-
asyncio.create_task(self.
|
72
|
+
self._set_startup_data_task = asyncio.create_task(self._set_startup_data())
|
71
73
|
|
72
|
-
async def
|
74
|
+
async def _set_startup_data(self) -> None:
|
73
75
|
async with self.get_http_client() as client:
|
74
76
|
await self.send_startup_data(client)
|
75
77
|
|
@@ -81,18 +83,21 @@ class ApitallyClient(ApitallyClientBase):
|
|
81
83
|
data = self.get_sync_data()
|
82
84
|
self._sync_data_queue.put_nowait((time.time(), data))
|
83
85
|
|
84
|
-
|
86
|
+
i = 0
|
85
87
|
while not self._sync_data_queue.empty():
|
86
88
|
timestamp, data = self._sync_data_queue.get_nowait()
|
87
89
|
try:
|
88
90
|
if (time_offset := time.time() - timestamp) <= MAX_QUEUE_TIME:
|
91
|
+
if i > 0:
|
92
|
+
await asyncio.sleep(random.uniform(0.1, 0.3))
|
89
93
|
data["time_offset"] = time_offset
|
90
94
|
await self._send_sync_data(client, data)
|
91
|
-
|
95
|
+
i += 1
|
92
96
|
except httpx.HTTPError:
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
self._sync_data_queue.put_nowait((timestamp, data))
|
98
|
+
break
|
99
|
+
finally:
|
100
|
+
self._sync_data_queue.task_done()
|
96
101
|
|
97
102
|
@retry(raise_on_giveup=False)
|
98
103
|
async def _send_startup_data(self, client: httpx.AsyncClient, data: Dict[str, Any]) -> None:
|
apitally/client/base.py
CHANGED
@@ -244,6 +244,7 @@ class ServerErrorCounter:
|
|
244
244
|
self.error_counts: Counter[ServerError] = Counter()
|
245
245
|
self.sentry_event_ids: Dict[ServerError, str] = {}
|
246
246
|
self._lock = threading.Lock()
|
247
|
+
self._tasks: Set[asyncio.Task] = set()
|
247
248
|
|
248
249
|
def add_server_error(self, consumer: Optional[str], method: str, path: str, exception: BaseException) -> None:
|
249
250
|
if not isinstance(exception, BaseException):
|
@@ -267,20 +268,20 @@ class ServerErrorCounter:
|
|
267
268
|
from sentry_sdk.scope import Scope
|
268
269
|
except ImportError:
|
269
270
|
return # pragma: no cover
|
270
|
-
if not hasattr(Scope, "get_isolation_scope") or not hasattr(Scope, "
|
271
|
+
if not hasattr(Scope, "get_isolation_scope") or not hasattr(Scope, "_last_event_id"):
|
271
272
|
# sentry-sdk < 2.2.0 is not supported
|
272
273
|
return # pragma: no cover
|
273
274
|
if Hub.current.client is None:
|
274
275
|
return # sentry-sdk not initialized
|
275
276
|
|
276
277
|
scope = Scope.get_isolation_scope()
|
277
|
-
if event_id := scope.
|
278
|
+
if event_id := scope._last_event_id:
|
278
279
|
self.sentry_event_ids[server_error] = event_id
|
279
280
|
return
|
280
281
|
|
281
282
|
async def _wait_for_sentry_event_id(scope: Scope) -> None:
|
282
283
|
i = 0
|
283
|
-
while not (event_id := scope.
|
284
|
+
while not (event_id := scope._last_event_id) and i < 100:
|
284
285
|
i += 1
|
285
286
|
await asyncio.sleep(0.001)
|
286
287
|
if event_id:
|
@@ -288,7 +289,9 @@ class ServerErrorCounter:
|
|
288
289
|
|
289
290
|
with contextlib.suppress(RuntimeError): # ignore no running loop
|
290
291
|
loop = asyncio.get_running_loop()
|
291
|
-
loop.create_task(_wait_for_sentry_event_id(scope))
|
292
|
+
task = loop.create_task(_wait_for_sentry_event_id(scope))
|
293
|
+
self._tasks.add(task)
|
294
|
+
task.add_done_callback(self._tasks.discard)
|
292
295
|
|
293
296
|
def get_and_reset_server_errors(self) -> List[Dict[str, Any]]:
|
294
297
|
data: List[Dict[str, Any]] = []
|
apitally/client/threading.py
CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import logging
|
4
4
|
import queue
|
5
|
+
import random
|
5
6
|
import time
|
6
7
|
from functools import partial
|
7
8
|
from threading import Event, Thread
|
@@ -38,7 +39,7 @@ try:
|
|
38
39
|
|
39
40
|
ipython = get_ipython() # type: ignore
|
40
41
|
except NameError:
|
41
|
-
from atexit import register as register_exit
|
42
|
+
from atexit import register as register_exit # type: ignore[assignment]
|
42
43
|
|
43
44
|
|
44
45
|
class ApitallyClient(ApitallyClientBase):
|
@@ -95,18 +96,21 @@ class ApitallyClient(ApitallyClientBase):
|
|
95
96
|
data = self.get_sync_data()
|
96
97
|
self._sync_data_queue.put_nowait((time.time(), data))
|
97
98
|
|
98
|
-
|
99
|
+
i = 0
|
99
100
|
while not self._sync_data_queue.empty():
|
100
101
|
timestamp, data = self._sync_data_queue.get_nowait()
|
101
102
|
try:
|
102
103
|
if (time_offset := time.time() - timestamp) <= MAX_QUEUE_TIME:
|
104
|
+
if i > 0:
|
105
|
+
time.sleep(random.uniform(0.1, 0.3))
|
103
106
|
data["time_offset"] = time_offset
|
104
107
|
self._send_sync_data(session, data)
|
105
|
-
|
108
|
+
i += 1
|
106
109
|
except requests.RequestException:
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
+
self._sync_data_queue.put_nowait((timestamp, data))
|
111
|
+
break
|
112
|
+
finally:
|
113
|
+
self._sync_data_queue.task_done()
|
110
114
|
|
111
115
|
@retry(raise_on_giveup=False)
|
112
116
|
def _send_startup_data(self, session: requests.Session, data: Dict[str, Any]) -> None:
|
apitally/django.py
CHANGED
@@ -94,7 +94,7 @@ class ApitallyMiddleware:
|
|
94
94
|
response = self.get_response(request)
|
95
95
|
response_time = time.perf_counter() - start_time
|
96
96
|
path = self.get_path(request)
|
97
|
-
if request.method is not None and path is not None:
|
97
|
+
if request.method is not None and request.method != "OPTIONS" and path is not None:
|
98
98
|
try:
|
99
99
|
consumer = self.get_consumer(request)
|
100
100
|
consumer_identifier = consumer.identifier if consumer else None
|
apitally/flask.py
CHANGED
@@ -93,7 +93,7 @@ class ApitallyMiddleware:
|
|
93
93
|
response_headers: Headers,
|
94
94
|
) -> None:
|
95
95
|
rule, is_handled_path = self.get_rule(environ)
|
96
|
-
if is_handled_path or not self.filter_unhandled_paths:
|
96
|
+
if (is_handled_path or not self.filter_unhandled_paths) and environ["REQUEST_METHOD"] != "OPTIONS":
|
97
97
|
consumer = self.get_consumer()
|
98
98
|
consumer_identifier = consumer.identifier if consumer else None
|
99
99
|
self.client.consumer_registry.add_or_update_consumer(consumer)
|
apitally/starlette.py
CHANGED
@@ -44,12 +44,15 @@ class ApitallyMiddleware(BaseHTTPMiddleware):
|
|
44
44
|
self.identify_consumer_callback = identify_consumer_callback
|
45
45
|
self.client = ApitallyClient(client_id=client_id, env=env)
|
46
46
|
self.client.start_sync_loop()
|
47
|
+
self._delayed_set_startup_data_task: Optional[asyncio.Task] = None
|
47
48
|
self.delayed_set_startup_data(app_version, openapi_url)
|
48
49
|
_register_shutdown_handler(app, self.client.handle_shutdown)
|
49
50
|
super().__init__(app)
|
50
51
|
|
51
52
|
def delayed_set_startup_data(self, app_version: Optional[str] = None, openapi_url: Optional[str] = None) -> None:
|
52
|
-
asyncio.create_task(
|
53
|
+
self._delayed_set_startup_data_task = asyncio.create_task(
|
54
|
+
self._delayed_set_startup_data(app_version, openapi_url)
|
55
|
+
)
|
53
56
|
|
54
57
|
async def _delayed_set_startup_data(
|
55
58
|
self, app_version: Optional[str] = None, openapi_url: Optional[str] = None
|
@@ -89,7 +92,7 @@ class ApitallyMiddleware(BaseHTTPMiddleware):
|
|
89
92
|
exception: Optional[BaseException] = None,
|
90
93
|
) -> None:
|
91
94
|
path_template, is_handled_path = self.get_path_template(request)
|
92
|
-
if is_handled_path or not self.filter_unhandled_paths:
|
95
|
+
if (is_handled_path or not self.filter_unhandled_paths) and request.method != "OPTIONS":
|
93
96
|
consumer = self.get_consumer(request)
|
94
97
|
consumer_identifier = consumer.identifier if consumer else None
|
95
98
|
self.client.consumer_registry.add_or_update_consumer(consumer)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
apitally/__init__.py,sha256=tCUnamQN48_YKI84dtjiVJI4cF4gypc8nKdvXAnhY_E,23
|
2
|
+
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
apitally/client/asyncio.py,sha256=Y5sbRLRnJCIJx9VQ2DGgQsYNKGvURV2U1y3VxHuPhgQ,4874
|
4
|
+
apitally/client/base.py,sha256=v4LSOYNIOoeL3KTVyBlXBY5LCXc79Lvu9yK5Y_KILSQ,15442
|
5
|
+
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
6
|
+
apitally/client/threading.py,sha256=cASa0C9nyRp5gf5IzCDj6TE-v8t8SW4zJ38W6NdJ3Q8,5204
|
7
|
+
apitally/common.py,sha256=GbVmnXxhRvV30d7CfCQ9r0AeXj14Mv9Jm_Yd1bRWP28,1088
|
8
|
+
apitally/django.py,sha256=Zw8a971UwGKaEMPUtmlBbjufAYwMkSjRSQlss8FDY-E,13795
|
9
|
+
apitally/django_ninja.py,sha256=dqQtnz2s8YWYHCwvkK5BjokjvpZJpPNhP0vng4kFtrQ,120
|
10
|
+
apitally/django_rest_framework.py,sha256=dqQtnz2s8YWYHCwvkK5BjokjvpZJpPNhP0vng4kFtrQ,120
|
11
|
+
apitally/fastapi.py,sha256=hEyYZsvIaA3OXZSSFdey5iqeEjfBPHgfNbyX8pLm7GI,123
|
12
|
+
apitally/flask.py,sha256=7TJIoAT91-bR_7gZkL0clDk-Whl-V21hbo4nASaDmB4,6447
|
13
|
+
apitally/litestar.py,sha256=sQcrHw-JV9AlpnXlrczmaDe0k6tD9PYQsc8nyQul8Ko,8802
|
14
|
+
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
+
apitally/starlette.py,sha256=HvphuX401h_ccqvOk2RKu7GGEai2WbGOjJ-WOE7-fWM,8781
|
16
|
+
apitally-0.11.3.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
17
|
+
apitally-0.11.3.dist-info/METADATA,sha256=oIrHGgHHvCpqj7LzkhpvT3nbMiTChIkPK5pPGBwH8C8,6994
|
18
|
+
apitally-0.11.3.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
19
|
+
apitally-0.11.3.dist-info/RECORD,,
|
apitally-0.11.1.dist-info/RECORD
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
apitally/__init__.py,sha256=G9OFdL1v_4dLDfk6I6taDNypM5bbO-JHAwilsu9LYgg,23
|
2
|
-
apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
apitally/client/asyncio.py,sha256=7Q1LRENXdN69pmRHc3IXdxWVKoWrfYoJiImj4ufyLcw,4692
|
4
|
-
apitally/client/base.py,sha256=IbAyC1VCONGA1B_Wj0jM82pkAn_cnVKe_iqR5xy3Zp0,15299
|
5
|
-
apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
|
6
|
-
apitally/client/threading.py,sha256=tgADSLbqQFnf5JqFmDT3ul3Jch00jZtU2MUJmOP22A4,5085
|
7
|
-
apitally/common.py,sha256=GbVmnXxhRvV30d7CfCQ9r0AeXj14Mv9Jm_Yd1bRWP28,1088
|
8
|
-
apitally/django.py,sha256=vqBloQH4WaxvIlVpDZoazPcj5ljFapi1kvUHRgkd0O4,13763
|
9
|
-
apitally/django_ninja.py,sha256=dqQtnz2s8YWYHCwvkK5BjokjvpZJpPNhP0vng4kFtrQ,120
|
10
|
-
apitally/django_rest_framework.py,sha256=dqQtnz2s8YWYHCwvkK5BjokjvpZJpPNhP0vng4kFtrQ,120
|
11
|
-
apitally/fastapi.py,sha256=hEyYZsvIaA3OXZSSFdey5iqeEjfBPHgfNbyX8pLm7GI,123
|
12
|
-
apitally/flask.py,sha256=KZxWN1xeXUazYYluu3aoKkZQ_aRljHmtjZi1AxvzpGw,6402
|
13
|
-
apitally/litestar.py,sha256=sQcrHw-JV9AlpnXlrczmaDe0k6tD9PYQsc8nyQul8Ko,8802
|
14
|
-
apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
15
|
-
apitally/starlette.py,sha256=ib73Xe8GUMDwR4uREZsJjtmJtgk7uNEiINr9ex3Atm0,8612
|
16
|
-
apitally-0.11.1.dist-info/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
|
17
|
-
apitally-0.11.1.dist-info/METADATA,sha256=IRzmUT79oBBhO_hZHUI5-b7bpx9GTyjY1PGWpM-rqIo,6994
|
18
|
-
apitally-0.11.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
19
|
-
apitally-0.11.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|