apitally 0.14.2__py3-none-any.whl → 0.14.4__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.
@@ -6,7 +6,7 @@ import random
6
6
  import time
7
7
  from contextlib import suppress
8
8
  from functools import partial
9
- from typing import Any, AsyncIterator, Dict, Optional, Tuple, Union
9
+ from typing import Any, AsyncIterator, Dict, Optional, Union
10
10
  from uuid import UUID
11
11
 
12
12
  import backoff
@@ -40,7 +40,7 @@ class ApitallyClient(ApitallyClientBase):
40
40
  self.proxy = proxy
41
41
  self._stop_sync_loop = False
42
42
  self._sync_loop_task: Optional[asyncio.Task] = None
43
- self._sync_data_queue: asyncio.Queue[Tuple[float, Dict[str, Any]]] = asyncio.Queue()
43
+ self._sync_data_queue: asyncio.Queue[Dict[str, Any]] = asyncio.Queue()
44
44
  self._set_startup_data_task: Optional[asyncio.Task] = None
45
45
 
46
46
  def get_http_client(self) -> httpx.AsyncClient:
@@ -103,20 +103,19 @@ class ApitallyClient(ApitallyClientBase):
103
103
 
104
104
  async def send_sync_data(self, client: httpx.AsyncClient) -> None:
105
105
  data = self.get_sync_data()
106
- self._sync_data_queue.put_nowait((time.time(), data))
106
+ self._sync_data_queue.put_nowait(data)
107
107
 
108
108
  i = 0
109
109
  while not self._sync_data_queue.empty():
110
- timestamp, data = self._sync_data_queue.get_nowait()
110
+ data = self._sync_data_queue.get_nowait()
111
111
  try:
112
- if (time_offset := time.time() - timestamp) <= MAX_QUEUE_TIME:
112
+ if time.time() - data["timestamp"] <= MAX_QUEUE_TIME:
113
113
  if i > 0:
114
- await asyncio.sleep(random.uniform(0.1, 0.3))
115
- data["time_offset"] = time_offset
114
+ await asyncio.sleep(random.uniform(0.1, 0.5))
116
115
  await self._send_sync_data(client, data)
117
116
  i += 1
118
117
  except httpx.HTTPError:
119
- self._sync_data_queue.put_nowait((timestamp, data))
118
+ self._sync_data_queue.put_nowait(data)
120
119
  break
121
120
  finally:
122
121
  self._sync_data_queue.task_done()
@@ -89,6 +89,7 @@ class ApitallyClientBase(ABC):
89
89
 
90
90
  def get_sync_data(self) -> Dict[str, Any]:
91
91
  data = {
92
+ "timestamp": time.time(),
92
93
  "requests": self.request_counter.get_and_reset_requests(),
93
94
  "validation_errors": self.validation_error_counter.get_and_reset_validation_errors(),
94
95
  "server_errors": self.server_error_counter.get_and_reset_server_errors(),
@@ -8,7 +8,7 @@ from functools import partial
8
8
  from io import BufferedReader
9
9
  from queue import Queue
10
10
  from threading import Event, Thread
11
- from typing import Any, Callable, Dict, Optional, Tuple
11
+ from typing import Any, Callable, Dict, Optional
12
12
  from uuid import UUID
13
13
 
14
14
  import backoff
@@ -58,7 +58,7 @@ class ApitallyClient(ApitallyClientBase):
58
58
  self.proxies = {"https": proxy} if proxy else None
59
59
  self._thread: Optional[Thread] = None
60
60
  self._stop_sync_loop = Event()
61
- self._sync_data_queue: Queue[Tuple[float, Dict[str, Any]]] = Queue()
61
+ self._sync_data_queue: Queue[Dict[str, Any]] = Queue()
62
62
 
63
63
  def start_sync_loop(self) -> None:
64
64
  self._stop_sync_loop.clear()
@@ -114,20 +114,19 @@ class ApitallyClient(ApitallyClientBase):
114
114
 
115
115
  def send_sync_data(self, session: requests.Session) -> None:
116
116
  data = self.get_sync_data()
117
- self._sync_data_queue.put_nowait((time.time(), data))
117
+ self._sync_data_queue.put_nowait(data)
118
118
 
119
119
  i = 0
120
120
  while not self._sync_data_queue.empty():
121
- timestamp, data = self._sync_data_queue.get_nowait()
121
+ data = self._sync_data_queue.get_nowait()
122
122
  try:
123
- if (time_offset := time.time() - timestamp) <= MAX_QUEUE_TIME:
123
+ if time.time() - data["timestamp"] <= MAX_QUEUE_TIME:
124
124
  if i > 0:
125
- time.sleep(random.uniform(0.1, 0.3))
126
- data["time_offset"] = time_offset
125
+ time.sleep(random.uniform(0.1, 0.5))
127
126
  self._send_sync_data(session, data)
128
127
  i += 1
129
128
  except requests.RequestException:
130
- self._sync_data_queue.put_nowait((timestamp, data))
129
+ self._sync_data_queue.put_nowait(data)
131
130
  break
132
131
  finally:
133
132
  self._sync_data_queue.task_done()
apitally/starlette.py CHANGED
@@ -222,12 +222,18 @@ class ApitallyMiddleware:
222
222
  },
223
223
  )
224
224
 
225
- @staticmethod
226
- def get_path(request: Request) -> Optional[str]:
227
- for route in request.app.routes:
228
- match, _ = route.matches(request.scope)
229
- if match == Match.FULL:
230
- return route.path
225
+ def get_path(self, request: Request, routes: Optional[list[BaseRoute]] = None) -> Optional[str]:
226
+ if routes is None:
227
+ routes = request.app.routes
228
+ for route in routes:
229
+ if hasattr(route, "routes"):
230
+ path = self.get_path(request, routes=route.routes)
231
+ if path is not None:
232
+ return path
233
+ elif hasattr(route, "path"):
234
+ match, _ = route.matches(request.scope)
235
+ if match == Match.FULL:
236
+ return request.scope.get("root_path", "") + route.path
231
237
  return None
232
238
 
233
239
  def get_consumer(self, request: Request) -> Optional[ApitallyConsumer]:
@@ -1,12 +1,13 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: apitally
3
- Version: 0.14.2
3
+ Version: 0.14.4
4
4
  Summary: Simple API monitoring & analytics for REST APIs built with FastAPI, Flask, Django, Starlette and Litestar.
5
5
  Project-URL: Homepage, https://apitally.io
6
6
  Project-URL: Documentation, https://docs.apitally.io
7
7
  Project-URL: Repository, https://github.com/apitally/apitally-py
8
8
  Author-email: Apitally <hello@apitally.io>
9
9
  License: MIT License
10
+ License-File: LICENSE
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
12
  Classifier: Environment :: Web Environment
12
13
  Classifier: Framework :: Django
@@ -68,9 +69,9 @@ Description-Content-Type: text/markdown
68
69
  </picture>
69
70
  </p>
70
71
 
71
- <p align="center"><b>API monitoring made easy.</b></p>
72
+ <p align="center"><b>Analytics, logging & monitoring for REST APIs.</b></p>
72
73
 
73
- <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>
74
+ <p align="center"><i>Apitally helps you understand how your APIs are being used and alerts you when things go wrong.<br>It's super easy to use and designed to protect your data privacy.</i></p>
74
75
 
75
76
  <p align="center">🔗 <b><a href="https://apitally.io" target="_blank">apitally.io</a></b></p>
76
77
 
@@ -100,7 +101,7 @@ the 📚 [documentation](https://docs.apitally.io).
100
101
  ## Key features
101
102
 
102
103
  - Middleware for different frameworks to capture metadata about API endpoints,
103
- requests and responses (no sensitive data is captured)
104
+ requests and responses
104
105
  - Non-blocking clients that aggregate and send captured data to Apitally in
105
106
  regular intervals
106
107
 
@@ -7,18 +7,18 @@ apitally/fastapi.py,sha256=IfKfgsmIY8_AtnuMTW2sW4qnkya61CAE2vBoIpcc9tk,169
7
7
  apitally/flask.py,sha256=Th5LsMsTKkWERPrKfSWPhzrp99tg0pDtKXgtlVLx3eo,9279
8
8
  apitally/litestar.py,sha256=hAH2-OVVXBDVY8LopfIGv30yYwi-71tSEsKd6648CYc,13098
9
9
  apitally/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- apitally/starlette.py,sha256=8WAWQPePJAYy-B5TrxawxAeUqBiSXD15Ay17i2B22jc,12500
10
+ apitally/starlette.py,sha256=VaT4-QVSYC0YX1U5kVI-dGROEd64IbjYU5lx5N16yf8,12852
11
11
  apitally/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- apitally/client/client_asyncio.py,sha256=lRvVKFU6eiapJnB9WrRq6Nuadx-n8NCKwQg-da1vPSM,7064
13
- apitally/client/client_base.py,sha256=w5AXAbg3hw5Qds5rovCZFtePB9bHNcJsr9l7kDgbroc,3733
14
- apitally/client/client_threading.py,sha256=MbytG8EopF84nmr5ShAZaq-VviSXYnBfBl7cRFRe1Kg,7479
12
+ apitally/client/client_asyncio.py,sha256=2RibaadLAEdl2i0yAb4dSEDq_r46w6HPFpZVzvt59aQ,6941
13
+ apitally/client/client_base.py,sha256=dXsaB7scd0wCd_DcdiUvNlDLZ2pWbWsGDnJ4fLYvJGg,3771
14
+ apitally/client/client_threading.py,sha256=Y8LlA_8LFHAuXym-T4bY_XFqXxiIDw9qFqO6K3FbZy0,7356
15
15
  apitally/client/consumers.py,sha256=w_AFQhVgdtJVt7pVySBvSZwQg-2JVqmD2JQtVBoMkus,2626
16
16
  apitally/client/logging.py,sha256=QMsKIIAFo92PNBUleeTgsrsQa7SEal-oJa1oOHUr1wI,507
17
17
  apitally/client/request_logging.py,sha256=5i7Gv4yP7iq4tBgw-ppkhlZd_OwMc719ZvWEm16TCvg,13047
18
18
  apitally/client/requests.py,sha256=RdJyvIqQGVHvS-wjpAPUwcO7byOJ6jO8dYqNTU2Furg,3685
19
19
  apitally/client/server_errors.py,sha256=axEhOxqV5SWjk0QCZTLVv2UMIaTfqPc81Typ4DXt66A,4646
20
20
  apitally/client/validation_errors.py,sha256=6G8WYWFgJs9VH9swvkPXJGuOJgymj5ooWA9OwjUTbuM,1964
21
- apitally-0.14.2.dist-info/METADATA,sha256=AUai6BWmcRZKC2mnN3om1SLIxPkyzPPMRAY-qdu1kus,7577
22
- apitally-0.14.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
23
- apitally-0.14.2.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
24
- apitally-0.14.2.dist-info/RECORD,,
21
+ apitally-0.14.4.dist-info/METADATA,sha256=bE7HFJBsn6IamIlJvehQhTd46k3Lx67imr2Yi7eM_PA,7570
22
+ apitally-0.14.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ apitally-0.14.4.dist-info/licenses/LICENSE,sha256=vbLzC-4TddtXX-_AFEBKMYWRlxC_MN0g66QhPxo8PgY,1065
24
+ apitally-0.14.4.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any