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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.11.1"
1
+ __version__ = "0.11.3"
@@ -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[Any]] = None
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._set_startup_data_task())
72
+ self._set_startup_data_task = asyncio.create_task(self._set_startup_data())
71
73
 
72
- async def _set_startup_data_task(self) -> None:
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
- failed_items = []
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
- self._sync_data_queue.task_done()
95
+ i += 1
92
96
  except httpx.HTTPError:
93
- failed_items.append((timestamp, data))
94
- for item in failed_items:
95
- self._sync_data_queue.put_nowait(item)
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, "last_event_id"):
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.last_event_id():
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.last_event_id()) and i < 100:
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]] = []
@@ -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
- failed_items = []
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
- self._sync_data_queue.task_done()
108
+ i += 1
106
109
  except requests.RequestException:
107
- failed_items.append((timestamp, data))
108
- for item in failed_items:
109
- self._sync_data_queue.put_nowait(item)
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(self._delayed_set_startup_data(app_version, openapi_url))
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: apitally
3
- Version: 0.11.1
3
+ Version: 0.11.3
4
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
@@ -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,,
@@ -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,,